This commit is contained in:
91
tests/specs/3way/activeSpeaker.spec.ts
Normal file
91
tests/specs/3way/activeSpeaker.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { ensureThreeParticipants, muteAudioAndCheck } from '../../helpers/participants';
|
||||
|
||||
describe('ActiveSpeaker', () => {
|
||||
it('testActiveSpeaker', async () => {
|
||||
await ensureThreeParticipants();
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await muteAudioAndCheck(p1, p2);
|
||||
await muteAudioAndCheck(p2, p1);
|
||||
await muteAudioAndCheck(p3, p1);
|
||||
|
||||
// participant1 becomes active speaker - check from participant2's perspective
|
||||
await testActiveSpeaker(p1, p2, p3);
|
||||
|
||||
// participant3 becomes active speaker - check from participant2's perspective
|
||||
await testActiveSpeaker(p3, p2, p1);
|
||||
|
||||
// participant2 becomes active speaker - check from participant1's perspective
|
||||
await testActiveSpeaker(p2, p1, p3);
|
||||
|
||||
// check the displayed speakers, there should be only one speaker
|
||||
await assertOneDominantSpeaker(p1);
|
||||
await assertOneDominantSpeaker(p2);
|
||||
await assertOneDominantSpeaker(p3);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tries to make given participant an active speaker by unmuting it.
|
||||
* Verifies from {@code participant2}'s perspective that the active speaker
|
||||
* has been displayed on the large video area. Mutes him back.
|
||||
*
|
||||
* @param {Participant} activeSpeaker - <tt>Participant</tt> instance of the participant who will be tested as an
|
||||
* active speaker.
|
||||
* @param {Participant} otherParticipant1 - <tt>Participant</tt> of the participant who will be observing and verifying
|
||||
* active speaker change.
|
||||
* @param {Participant} otherParticipant2 - Used only to print some debugging info.
|
||||
*/
|
||||
async function testActiveSpeaker(
|
||||
activeSpeaker: Participant, otherParticipant1: Participant, otherParticipant2: Participant) {
|
||||
activeSpeaker.log(`Start testActiveSpeaker for participant: ${activeSpeaker.name}`);
|
||||
|
||||
const speakerEndpoint = await activeSpeaker.getEndpointId();
|
||||
|
||||
// just a debug print to go in logs
|
||||
activeSpeaker.log('Unmuting in testActiveSpeaker');
|
||||
|
||||
// Unmute
|
||||
await activeSpeaker.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// just a debug print to go in logs
|
||||
otherParticipant1.log(`Participant unmuted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
otherParticipant2.log(`Participant unmuted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
|
||||
await activeSpeaker.getFilmstrip().assertAudioMuteIconIsDisplayed(activeSpeaker, true);
|
||||
|
||||
// Verify that the user is now an active speaker from otherParticipant1's perspective
|
||||
const otherParticipant1Driver = otherParticipant1.driver;
|
||||
|
||||
await otherParticipant1Driver.waitUntil(
|
||||
async () => await otherParticipant1.getLargeVideo().getResource() === speakerEndpoint,
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: 'Active speaker not displayed on large video.'
|
||||
});
|
||||
|
||||
// just a debug print to go in logs
|
||||
activeSpeaker.log('Muting in testActiveSpeaker');
|
||||
|
||||
// Mute back again
|
||||
await activeSpeaker.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// just a debug print to go in logs
|
||||
otherParticipant1.log(`Participant muted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
otherParticipant2.log(`Participant muted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
|
||||
await otherParticipant1.getFilmstrip().assertAudioMuteIconIsDisplayed(activeSpeaker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the number of small videos with the dominant speaker
|
||||
* indicator displayed equals 1.
|
||||
*
|
||||
* @param {Participant} participant - The participant to check.
|
||||
*/
|
||||
async function assertOneDominantSpeaker(participant: Participant) {
|
||||
expect(await participant.driver.$$(
|
||||
'//span[not(contains(@class, "tile-view"))]//span[contains(@class,"dominant-speaker")]').length).toBe(1);
|
||||
}
|
||||
297
tests/specs/3way/audioVideoModeration.spec.ts
Normal file
297
tests/specs/3way/audioVideoModeration.spec.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import { Participant } from '../../helpers/Participant';
|
||||
import { config } from '../../helpers/TestsConfig';
|
||||
import {
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants, ensureTwoParticipants,
|
||||
hangupAllParticipants,
|
||||
unmuteAudioAndCheck,
|
||||
unmuteVideoAndCheck
|
||||
} from '../../helpers/participants';
|
||||
|
||||
describe('AVModeration', () => {
|
||||
|
||||
it('check for moderators', async () => {
|
||||
// if all 3 participants are moderators, skip this test
|
||||
await ensureThreeParticipants();
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
if (!await p1.isModerator()
|
||||
|| (await p1.isModerator() && await p2.isModerator() && await p3.isModerator())) {
|
||||
ctx.skipSuiteTests = true;
|
||||
}
|
||||
});
|
||||
|
||||
it('check audio enable/disable', async () => {
|
||||
const { p1, p3 } = ctx;
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
// Here we want to try unmuting and check that we are still muted.
|
||||
await tryToAudioUnmuteAndCheck(p3, p1);
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStopAudioModeration();
|
||||
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
await unmuteAudioAndCheck(p3, p1);
|
||||
});
|
||||
|
||||
it('check video enable/disable', async () => {
|
||||
const { p1, p3 } = ctx;
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
// Here we want to try unmuting and check that we are still muted.
|
||||
await tryToVideoUnmuteAndCheck(p3, p1);
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStopVideoModeration();
|
||||
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
await unmuteVideoAndCheck(p3, p1);
|
||||
});
|
||||
|
||||
it('unmute by moderator', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await unmuteByModerator(p1, p3, true, true);
|
||||
|
||||
// moderation is stopped at this point, make sure participants 1 & 2 are also unmuted,
|
||||
// participant3 was unmuted by unmuteByModerator
|
||||
await unmuteAudioAndCheck(p2, p1);
|
||||
await unmuteVideoAndCheck(p2, p1);
|
||||
|
||||
// make sure p1 is not muted after turning on and then off the AV moderation
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p1, true);
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2, true);
|
||||
});
|
||||
|
||||
it('hangup and change moderator', async () => {
|
||||
// no moderator switching if jaas is available.
|
||||
if (config.iframe.usesJaas) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([ ctx.p2.hangup(), ctx.p3.hangup() ]);
|
||||
|
||||
await ensureThreeParticipants();
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
await p3.getToolbar().clickAudioMuteButton();
|
||||
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
|
||||
await p2.getToolbar().clickRaiseHandButton();
|
||||
await p3.getToolbar().clickRaiseHandButton();
|
||||
|
||||
await p1.hangup();
|
||||
|
||||
// we don't use ensureThreeParticipants to avoid all meeting join checks
|
||||
// all participants are muted and checks for media will fail
|
||||
await ensureOneParticipant();
|
||||
|
||||
// After p1 re-joins either p2 or p3 is promoted to moderator. They should still be muted.
|
||||
const isP2Moderator = await p2.isModerator();
|
||||
const moderator = isP2Moderator ? p2 : p3;
|
||||
const nonModerator = isP2Moderator ? p3 : p2;
|
||||
const moderatorParticipantsPane = moderator.getParticipantsPane();
|
||||
const nonModeratorParticipantsPane = nonModerator.getParticipantsPane();
|
||||
|
||||
await moderatorParticipantsPane.assertVideoMuteIconIsDisplayed(moderator);
|
||||
await nonModeratorParticipantsPane.assertVideoMuteIconIsDisplayed(nonModerator);
|
||||
|
||||
await moderatorParticipantsPane.allowVideo(nonModerator);
|
||||
await moderatorParticipantsPane.askToUnmute(nonModerator, false);
|
||||
|
||||
await nonModerator.getNotifications().waitForAskToUnmuteNotification();
|
||||
|
||||
await unmuteAudioAndCheck(nonModerator, p1);
|
||||
await unmuteVideoAndCheck(nonModerator, p1);
|
||||
|
||||
await moderatorParticipantsPane.clickContextMenuButton();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStopAudioModeration();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStopVideoModeration();
|
||||
});
|
||||
it('grant moderator', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureThreeParticipants();
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
|
||||
await p1.getFilmstrip().grantModerator(p3);
|
||||
|
||||
await p3.driver.waitUntil(
|
||||
() => p3.isModerator(), {
|
||||
timeout: 5000,
|
||||
timeoutMsg: `${p3.name} is not moderator`
|
||||
});
|
||||
|
||||
await unmuteByModerator(p3, p2, false, true);
|
||||
});
|
||||
it('ask to unmute', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// mute p2
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// ask p2 to unmute
|
||||
await p1.getParticipantsPane().askToUnmute(p2, true);
|
||||
|
||||
await p2.getNotifications().waitForAskToUnmuteNotification();
|
||||
|
||||
await p1.getParticipantsPane().close();
|
||||
});
|
||||
it('remove from whitelist', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await unmuteByModerator(p1, p2, true, false);
|
||||
|
||||
// p1 mute audio on p2 and check
|
||||
await p1.getFilmstrip().muteAudio(p2);
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
|
||||
// we try to unmute and test it that it was still muted
|
||||
await tryToAudioUnmuteAndCheck(p2, p1);
|
||||
|
||||
// stop video and check
|
||||
await p1.getFilmstrip().muteVideo(p2);
|
||||
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
|
||||
await tryToVideoUnmuteAndCheck(p2, p1);
|
||||
});
|
||||
it('join moderated', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureOneParticipant();
|
||||
|
||||
const p1ParticipantsPane = ctx.p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
// join with second participant and check
|
||||
await ensureTwoParticipants({
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p2.getNotifications().closeYouAreMutedNotification();
|
||||
await tryToAudioUnmuteAndCheck(p2, p1);
|
||||
await tryToVideoUnmuteAndCheck(p2, p1);
|
||||
|
||||
// asked to unmute and check
|
||||
await unmuteByModerator(p1, p2, false, false);
|
||||
|
||||
// mute and check
|
||||
await p1.getFilmstrip().muteAudio(p2);
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
|
||||
await tryToAudioUnmuteAndCheck(p2, p1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks a user can unmute after being asked by moderator.
|
||||
* @param moderator - The participant that is moderator.
|
||||
* @param participant - The participant being asked to unmute.
|
||||
* @param turnOnModeration - if we want to turn on moderation before testing (when it is currently off).
|
||||
* @param stopModeration - true if moderation to be stopped when done.
|
||||
*/
|
||||
async function unmuteByModerator(
|
||||
moderator: Participant,
|
||||
participant: Participant,
|
||||
turnOnModeration: boolean,
|
||||
stopModeration: boolean) {
|
||||
const moderatorParticipantsPane = moderator.getParticipantsPane();
|
||||
|
||||
if (turnOnModeration) {
|
||||
await moderatorParticipantsPane.clickContextMenuButton();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
|
||||
await moderatorParticipantsPane.close();
|
||||
}
|
||||
|
||||
// raise hand to speak
|
||||
await participant.getToolbar().clickRaiseHandButton();
|
||||
await moderator.getNotifications().waitForRaisedHandNotification();
|
||||
|
||||
// ask participant to unmute
|
||||
await moderatorParticipantsPane.allowVideo(participant);
|
||||
await moderatorParticipantsPane.askToUnmute(participant, false);
|
||||
await participant.getNotifications().waitForAskToUnmuteNotification();
|
||||
|
||||
await unmuteAudioAndCheck(participant, moderator);
|
||||
await unmuteVideoAndCheck(participant, moderator);
|
||||
|
||||
if (stopModeration) {
|
||||
await moderatorParticipantsPane.clickContextMenuButton();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStopAudioModeration();
|
||||
await moderatorParticipantsPane.getAVModerationMenu().clickStopVideoModeration();
|
||||
|
||||
await moderatorParticipantsPane.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of moderation, tries to audio unmute but stays muted.
|
||||
* Checks locally and remotely that this is still the case.
|
||||
* @param participant
|
||||
* @param observer
|
||||
*/
|
||||
async function tryToAudioUnmuteAndCheck(participant: Participant, observer: Participant) {
|
||||
// try to audio unmute and check
|
||||
await participant.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// Check local audio muted icon state
|
||||
await participant.getFilmstrip().assertAudioMuteIconIsDisplayed(participant);
|
||||
await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(participant);
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of moderation, tries to video unmute but stays muted.
|
||||
* Checks locally and remotely that this is still the case.
|
||||
* @param participant
|
||||
* @param observer
|
||||
*/
|
||||
async function tryToVideoUnmuteAndCheck(participant: Participant, observer: Participant) {
|
||||
// try to video unmute and check
|
||||
await participant.getToolbar().clickVideoUnmuteButton();
|
||||
|
||||
// Check local audio muted icon state
|
||||
await participant.getParticipantsPane().assertVideoMuteIconIsDisplayed(participant);
|
||||
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(participant);
|
||||
}
|
||||
212
tests/specs/3way/avatars.spec.ts
Normal file
212
tests/specs/3way/avatars.spec.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import {
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
unmuteVideoAndCheck
|
||||
} from '../../helpers/participants';
|
||||
|
||||
const EMAIL = 'support@jitsi.org';
|
||||
const HASH = '38f014e4b7dde0f64f8157d26a8c812e';
|
||||
|
||||
describe('Avatar', () => {
|
||||
it('setup the meeting', () =>
|
||||
ensureTwoParticipants({
|
||||
skipDisplayName: true
|
||||
})
|
||||
);
|
||||
|
||||
it('change and check', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// check default avatar for p1 on p2
|
||||
await p2.assertDefaultAvatarExist(p1);
|
||||
|
||||
await p1.getToolbar().clickProfileButton();
|
||||
|
||||
const settings = p1.getSettingsDialog();
|
||||
|
||||
await settings.waitForDisplay();
|
||||
await settings.setEmail(EMAIL);
|
||||
await settings.submit();
|
||||
|
||||
// check if the local avatar in the toolbar menu has changed
|
||||
await p1.driver.waitUntil(
|
||||
async () => (await p1.getToolbar().getProfileImage())?.includes(HASH), {
|
||||
timeout: 3000, // give more time for the initial download of the image
|
||||
timeoutMsg: 'Avatar has not changed for p1'
|
||||
});
|
||||
|
||||
// check if the avatar in the local thumbnail has changed
|
||||
expect(await p1.getLocalVideoAvatar()).toContain(HASH);
|
||||
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
async () => (await p2.getFilmstrip().getAvatar(p1EndpointId))?.includes(HASH), {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'Avatar has not changed for p1 on p2'
|
||||
});
|
||||
|
||||
// check if the avatar in the large video has changed
|
||||
expect(await p2.getLargeVideo().getAvatar()).toContain(HASH);
|
||||
|
||||
// we check whether the default avatar of participant2 is displayed on both sides
|
||||
await p1.assertDefaultAvatarExist(p2);
|
||||
await p2.assertDefaultAvatarExist(p2);
|
||||
|
||||
// the problem on FF where we can send keys to the input field,
|
||||
// and the m from the text can mute the call, check whether we are muted
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p1, true);
|
||||
});
|
||||
|
||||
it('when video muted', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await ctx.p2.hangup();
|
||||
|
||||
// Mute p1's video
|
||||
await p1.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
async () => (await p1.getLargeVideo().getAvatar())?.includes(HASH), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'Avatar on large video did not change'
|
||||
});
|
||||
|
||||
const p1LargeSrc = await p1.getLargeVideo().getAvatar();
|
||||
const p1ThumbSrc = await p1.getLocalVideoAvatar();
|
||||
|
||||
// Check if avatar on large video is the same as on local thumbnail
|
||||
expect(p1ThumbSrc).toBe(p1LargeSrc);
|
||||
|
||||
// Join p2
|
||||
await ensureTwoParticipants({
|
||||
skipDisplayName: true
|
||||
});
|
||||
const { p2 } = ctx;
|
||||
|
||||
// Verify that p1 is muted from the perspective of p2
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
await p2.getFilmstrip().pinParticipant(p1);
|
||||
|
||||
// Check if p1's avatar is on large video now
|
||||
await p2.driver.waitUntil(
|
||||
async () => await p2.getLargeVideo().getAvatar() === p1LargeSrc, {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'Avatar on large video did not change'
|
||||
});
|
||||
|
||||
// p1 pins p2's video
|
||||
await p1.getFilmstrip().pinParticipant(p2);
|
||||
|
||||
// Check if avatar is displayed on p1's local video thumbnail
|
||||
await p1.assertThumbnailShowsAvatar(p1, false, false, true);
|
||||
|
||||
// Unmute - now local avatar should be hidden and local video displayed
|
||||
await unmuteVideoAndCheck(p1, p2);
|
||||
|
||||
await p1.asserLocalThumbnailShowsVideo();
|
||||
|
||||
// Now both p1 and p2 have video muted
|
||||
await p1.getToolbar().clickVideoMuteButton();
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
|
||||
// Start the third participant
|
||||
await ensureThreeParticipants({
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// When the first participant is FF because of their audio mic feed it will never become dominant speaker
|
||||
// and no audio track will be received by the third participant and video is muted,
|
||||
// that's why we need to do a different check that expects any track just from p2
|
||||
if (p1.driver.isFirefox) {
|
||||
await Promise.all([ p2.waitForRemoteStreams(1), p3.waitForRemoteStreams(1) ]);
|
||||
} else {
|
||||
await Promise.all([ p2.waitForRemoteStreams(2), p3.waitForRemoteStreams(2) ]);
|
||||
}
|
||||
|
||||
// Pin local video and verify avatars are displayed
|
||||
await p3.getFilmstrip().pinParticipant(p3);
|
||||
|
||||
await p3.assertThumbnailShowsAvatar(p1, false, false, true);
|
||||
await p3.assertThumbnailShowsAvatar(p2, false, true);
|
||||
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
expect(await p3.getFilmstrip().getAvatar(p1EndpointId)).toBe(p1ThumbSrc);
|
||||
|
||||
// Click on p1's video
|
||||
await p3.getFilmstrip().pinParticipant(p1);
|
||||
|
||||
// The avatar should be on large video and display name instead of an avatar, local video displayed
|
||||
await p3.driver.waitUntil(
|
||||
async () => await p3.getLargeVideo().getResource() === p1EndpointId, {
|
||||
timeout: 2000,
|
||||
timeoutMsg: `Large video did not switch to ${p1.name}`
|
||||
});
|
||||
|
||||
await p3.assertDisplayNameVisibleOnStage(
|
||||
await p3.getFilmstrip().getRemoteDisplayName(p1EndpointId));
|
||||
|
||||
// p2 has the default avatar
|
||||
await p3.assertThumbnailShowsAvatar(p2, false, true);
|
||||
await p3.assertThumbnailShowsAvatar(p3, true);
|
||||
|
||||
// Click on p2's video
|
||||
await p3.getFilmstrip().pinParticipant(p2);
|
||||
|
||||
// The avatar should be on large video and display name instead of an avatar, local video displayed
|
||||
await p3.driver.waitUntil(
|
||||
async () => await p3.getLargeVideo().getResource() === p2EndpointId, {
|
||||
timeout: 2000,
|
||||
timeoutMsg: `Large video did not switch to ${p2.name}`
|
||||
});
|
||||
|
||||
await p3.assertDisplayNameVisibleOnStage(
|
||||
await p3.getFilmstrip().getRemoteDisplayName(p2EndpointId)
|
||||
);
|
||||
|
||||
await p3.assertThumbnailShowsAvatar(p1, false, false, true);
|
||||
await p3.assertThumbnailShowsAvatar(p3, true);
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
// Unmute p1's and p2's videos
|
||||
await unmuteVideoAndCheck(p1, p2);
|
||||
});
|
||||
|
||||
it('email persistence', async () => {
|
||||
let { p1 } = ctx;
|
||||
|
||||
if (p1.driver.isFirefox) {
|
||||
// strangely this test when FF is involved, missing source mapping from jvb
|
||||
// and fails with an error of: expected number of remote streams:1 in 15s for participant1
|
||||
return;
|
||||
}
|
||||
|
||||
await p1.getToolbar().clickProfileButton();
|
||||
|
||||
expect(await p1.getSettingsDialog().getEmail()).toBe(EMAIL);
|
||||
|
||||
await p1.hangup();
|
||||
|
||||
await ensureTwoParticipants({
|
||||
skipDisplayName: true
|
||||
});
|
||||
p1 = ctx.p1;
|
||||
|
||||
await p1.getToolbar().clickProfileButton();
|
||||
|
||||
expect(await p1.getSettingsDialog().getEmail()).toBe(EMAIL);
|
||||
});
|
||||
});
|
||||
481
tests/specs/3way/breakoutRooms.spec.ts
Normal file
481
tests/specs/3way/breakoutRooms.spec.ts
Normal file
@@ -0,0 +1,481 @@
|
||||
import type { ChainablePromiseElement } from 'webdriverio';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import {
|
||||
checkSubject,
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
|
||||
const MAIN_ROOM_NAME = 'Main room';
|
||||
const BREAKOUT_ROOMS_LIST_ID = 'breakout-rooms-list';
|
||||
const LIST_ITEM_CONTAINER = 'list-item-container';
|
||||
|
||||
describe('BreakoutRooms', () => {
|
||||
it('check support', async () => {
|
||||
await ensureTwoParticipants();
|
||||
|
||||
if (!await ctx.p1.isBreakoutRoomsSupported()) {
|
||||
ctx.skipSuiteTests = true;
|
||||
}
|
||||
});
|
||||
|
||||
it('add breakout room', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// there should be no breakout rooms initially, list is sent with a small delay
|
||||
await p1.driver.pause(2000);
|
||||
expect(await p1BreakoutRooms.getRoomsCount()).toBe(0);
|
||||
|
||||
// add one breakout room
|
||||
await p1BreakoutRooms.addBreakoutRoom();
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1BreakoutRooms.getRoomsCount() === 1, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'No breakout room added for p1'
|
||||
});
|
||||
|
||||
|
||||
// second participant should also see one breakout room
|
||||
await p2.driver.waitUntil(
|
||||
async () => await p2.getBreakoutRooms().getRoomsCount() === 1, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'No breakout room seen by p2'
|
||||
});
|
||||
});
|
||||
|
||||
it('join breakout room', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// there should be one breakout room
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1BreakoutRooms.getRoomsCount() === 1, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'No breakout room seen by p1'
|
||||
});
|
||||
|
||||
const roomsList = await p1BreakoutRooms.getRooms();
|
||||
|
||||
expect(roomsList.length).toBe(1);
|
||||
|
||||
// join the room
|
||||
await roomsList[0].joinRoom();
|
||||
|
||||
// the participant should see the main room as the only breakout room
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p1BreakoutRooms.getRoomsCount() !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].name === MAIN_ROOM_NAME;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'P1 did not join breakout room'
|
||||
});
|
||||
|
||||
// the second participant should see one participant in the breakout room
|
||||
await p2.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p2.getBreakoutRooms().getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'P2 is not seeing p1 in the breakout room'
|
||||
});
|
||||
});
|
||||
|
||||
it('leave breakout room', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// leave room
|
||||
await p1BreakoutRooms.leaveBreakoutRoom();
|
||||
|
||||
// there should be one breakout room and that should not be the main room
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p1BreakoutRooms.getRoomsCount() !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].name !== MAIN_ROOM_NAME;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'P1 did not leave breakout room'
|
||||
});
|
||||
|
||||
// the second participant should see no participants in the breakout room
|
||||
await p2.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p2.getBreakoutRooms().getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 0;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'P2 is seeing p1 in the breakout room'
|
||||
});
|
||||
});
|
||||
|
||||
it('remove breakout room', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// remove the room
|
||||
await (await p1BreakoutRooms.getRooms())[0].removeRoom();
|
||||
|
||||
// there should be no breakout rooms
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1BreakoutRooms.getRoomsCount() === 0, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'Breakout room was not removed for p1'
|
||||
});
|
||||
|
||||
// the second participant should also see no breakout rooms
|
||||
await p2.driver.waitUntil(
|
||||
async () => await p2.getBreakoutRooms().getRoomsCount() === 0, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'Breakout room was not removed for p2'
|
||||
});
|
||||
});
|
||||
|
||||
it('auto assign', async () => {
|
||||
await ensureThreeParticipants();
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// create two rooms
|
||||
await p1BreakoutRooms.addBreakoutRoom();
|
||||
await p1BreakoutRooms.addBreakoutRoom();
|
||||
|
||||
// there should be two breakout rooms
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1BreakoutRooms.getRoomsCount() === 2, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'Breakout room was not created by p1'
|
||||
});
|
||||
|
||||
// auto assign participants to rooms
|
||||
await p1BreakoutRooms.autoAssignToBreakoutRooms();
|
||||
|
||||
// each room should have one participant
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p1BreakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1 && list[1].participantCount === 1;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'P1 did not auto assigned participants to breakout rooms'
|
||||
});
|
||||
|
||||
// the second participant should see one participant in the main room
|
||||
const p2BreakoutRooms = p2.getBreakoutRooms();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p2BreakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p2BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1 && list[1].participantCount === 1
|
||||
&& (list[0].name === MAIN_ROOM_NAME || list[1].name === MAIN_ROOM_NAME);
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'P2 is not seeing p1 in the main room'
|
||||
});
|
||||
});
|
||||
|
||||
it('close breakout room', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// there should be two non-empty breakout rooms
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p1BreakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1 && list[1].participantCount === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'P1 is not seeing two breakout rooms'
|
||||
});
|
||||
|
||||
// close the first room
|
||||
await (await p1BreakoutRooms.getRooms())[0].closeRoom();
|
||||
|
||||
// there should be two rooms and first one should be empty
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
if (await p1BreakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 0 || list[1].participantCount === 0;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'P1 is not seeing an empty breakout room'
|
||||
});
|
||||
|
||||
// there should be two participants in the main room, either p2 or p3 got moved to the main room
|
||||
const checkParticipants = (p: Participant) =>
|
||||
p.driver.waitUntil(
|
||||
async () => {
|
||||
const isInBreakoutRoom = await p.isInBreakoutRoom();
|
||||
const breakoutRooms = p.getBreakoutRooms();
|
||||
|
||||
if (isInBreakoutRoom) {
|
||||
if (await breakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await breakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list.every(r => { // eslint-disable-line arrow-body-style
|
||||
return r.name === MAIN_ROOM_NAME ? r.participantCount === 2 : r.participantCount === 0;
|
||||
});
|
||||
}
|
||||
|
||||
if (await breakoutRooms.getRoomsCount() !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const list = await breakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount + list[1].participantCount === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: `${p.name} is not seeing an empty breakout room and one with one participant`
|
||||
});
|
||||
|
||||
await checkParticipants(p2);
|
||||
await checkParticipants(p3);
|
||||
});
|
||||
|
||||
it('send participants to breakout room', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
// because the participants rejoin so fast, the meeting is not properly ended,
|
||||
// so the previous breakout rooms would still be there.
|
||||
// To avoid this issue we use a different meeting
|
||||
// Respect room name suffix as it is important in multi-shard testing
|
||||
ctx.roomName += `new-${ctx.roomName}`;
|
||||
|
||||
await ensureTwoParticipants();
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// there should be no breakout rooms
|
||||
expect(await p1BreakoutRooms.getRoomsCount()).toBe(0);
|
||||
|
||||
// add one breakout room
|
||||
await p1BreakoutRooms.addBreakoutRoom();
|
||||
|
||||
// there should be one empty room
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1BreakoutRooms.getRoomsCount() === 1
|
||||
&& (await p1BreakoutRooms.getRooms())[0].participantCount === 0, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'No breakout room added for p1'
|
||||
});
|
||||
|
||||
// send the second participant to the first breakout room
|
||||
await p1BreakoutRooms.sendParticipantToBreakoutRoom(p2, (await p1BreakoutRooms.getRooms())[0].name);
|
||||
|
||||
// there should be one room with one participant
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'P1 is not seeing p2 in the breakout room'
|
||||
});
|
||||
});
|
||||
|
||||
it('collapse breakout room', async () => {
|
||||
const { p1 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// there should be one breakout room with one participant
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'P1 is not seeing p2 in the breakout room'
|
||||
});
|
||||
|
||||
// get id of the breakout room participant
|
||||
const breakoutList = p1.driver.$(`#${BREAKOUT_ROOMS_LIST_ID}`);
|
||||
const breakoutRoomItem = await breakoutList.$$(`.${LIST_ITEM_CONTAINER}`).find(
|
||||
async el => {
|
||||
const id = await el.getAttribute('id');
|
||||
|
||||
return id !== '' && id !== null;
|
||||
}) as ChainablePromiseElement;
|
||||
|
||||
const pId = await breakoutRoomItem.getAttribute('id');
|
||||
const breakoutParticipant = p1.driver.$(`//div[@id="${pId}"]`);
|
||||
|
||||
expect(await breakoutParticipant.isDisplayed()).toBe(true);
|
||||
|
||||
// collapse the first
|
||||
await (await p1BreakoutRooms.getRooms())[0].collapse();
|
||||
|
||||
// the participant should not be visible
|
||||
expect(await breakoutParticipant.isDisplayed()).toBe(false);
|
||||
|
||||
// the collapsed room should still have one participant
|
||||
expect((await p1BreakoutRooms.getRooms())[0].participantCount).toBe(1);
|
||||
});
|
||||
|
||||
it('rename breakout room', async () => {
|
||||
const myNewRoomName = `breakout-${crypto.randomUUID()}`;
|
||||
const { p1, p2 } = ctx;
|
||||
const p1BreakoutRooms = p1.getBreakoutRooms();
|
||||
|
||||
// let's rename breakout room and see it in local and remote
|
||||
await (await p1BreakoutRooms.getRooms())[0].renameRoom(myNewRoomName);
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].name === myNewRoomName;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'The breakout room was not renamed for p1'
|
||||
});
|
||||
|
||||
await checkSubject(p2, myNewRoomName);
|
||||
|
||||
const p2BreakoutRooms = p2.getBreakoutRooms();
|
||||
|
||||
// leave room
|
||||
await p2BreakoutRooms.leaveBreakoutRoom();
|
||||
|
||||
// there should be one empty room
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 0;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'The breakout room not found or not empty for p1'
|
||||
});
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p2BreakoutRooms.getRooms();
|
||||
|
||||
return list?.length === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'The breakout room not seen by p2'
|
||||
});
|
||||
|
||||
expect((await p2BreakoutRooms.getRooms())[0].name).toBe(myNewRoomName);
|
||||
|
||||
// send the second participant to the first breakout room
|
||||
await p1BreakoutRooms.sendParticipantToBreakoutRoom(p2, myNewRoomName);
|
||||
|
||||
// there should be one room with one participant
|
||||
await p1.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p1BreakoutRooms.getRooms();
|
||||
|
||||
if (list?.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return list[0].participantCount === 1;
|
||||
}, {
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'The breakout room was not rename for p1'
|
||||
});
|
||||
|
||||
await checkSubject(p2, myNewRoomName);
|
||||
});
|
||||
});
|
||||
128
tests/specs/3way/codecSelection.spec.ts
Normal file
128
tests/specs/3way/codecSelection.spec.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
|
||||
describe('Codec selection', () => {
|
||||
it('asymmetric codecs', async () => {
|
||||
await ensureOneParticipant({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP9', 'VP8', 'AV1' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP8', 'VP9', 'AV1' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// Check if media is playing on both endpoints.
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
|
||||
// Check if p1 is sending VP9 and p2 is sending VP8 as per their codec preferences.
|
||||
// Except on Firefox because it doesn't support VP9 encode.
|
||||
if (p1.driver.isFirefox) {
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
} else {
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
|
||||
}
|
||||
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
});
|
||||
|
||||
it('asymmetric codecs with AV1', async () => {
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
// Check if media is playing on p3.
|
||||
expect(await p3.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
|
||||
const majorVersion = parseInt(p1.driver.capabilities.browserVersion || '0', 10);
|
||||
|
||||
// Check if p1 is encoding in VP9, p2 in VP8 and p3 in AV1 as per their codec preferences.
|
||||
// Except on Firefox because it doesn't support VP9 encode.
|
||||
if (p1.driver.isFirefox) {
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
} else {
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
|
||||
}
|
||||
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
|
||||
// If there is a Firefox ep in the call, all other eps will switch to VP9.
|
||||
if (p1.driver.isFirefox && majorVersion < 136) {
|
||||
expect(await p3.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
|
||||
} else {
|
||||
expect(await p3.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingAv1())).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('codec switch over', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP9', 'VP8', 'AV1' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// Disable this test on Firefox because it doesn't support VP9 encode.
|
||||
if (p1.driver.isFirefox) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if p1 and p2 are encoding in VP9 which is the default codec.
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP8' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p3 } = ctx;
|
||||
|
||||
// Check if all three participants are encoding in VP8 now.
|
||||
expect(await p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
expect(await p3.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
// Check of p1 and p2 have switched to VP9.
|
||||
await p1.driver.waitUntil(
|
||||
() => p1.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
|
||||
{
|
||||
timeout: 10000,
|
||||
timeoutMsg: 'p1 did not switch back to VP9'
|
||||
}
|
||||
);
|
||||
await p2.driver.waitUntil(
|
||||
() => p2.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
|
||||
{
|
||||
timeout: 10000,
|
||||
timeoutMsg: 'p1 did not switch back to VP9'
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
93
tests/specs/3way/followMe.spec.ts
Normal file
93
tests/specs/3way/followMe.spec.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Follow Me', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getToolbar().clickSettingsButton();
|
||||
|
||||
const settings = p1.getSettingsDialog();
|
||||
|
||||
await settings.waitForDisplay();
|
||||
await settings.setFollowMe(true);
|
||||
await settings.submit();
|
||||
});
|
||||
|
||||
it('follow me checkbox visible only for moderators', async () => {
|
||||
const { p2 } = ctx;
|
||||
|
||||
if (!await p2.isModerator()) {
|
||||
await p2.getToolbar().clickSettingsButton();
|
||||
|
||||
const settings = p2.getSettingsDialog();
|
||||
|
||||
await settings.waitForDisplay();
|
||||
expect(await settings.isFollowMeDisplayed()).toBe(false);
|
||||
|
||||
await settings.clickCloseButton();
|
||||
}
|
||||
});
|
||||
|
||||
it('filmstrip commands', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
const p1Filmstrip = p1.getFilmstrip();
|
||||
const p2Filmstrip = p2.getFilmstrip();
|
||||
|
||||
await p1Filmstrip.toggle();
|
||||
|
||||
await p1Filmstrip.assertRemoteVideosHidden();
|
||||
await p2Filmstrip.assertRemoteVideosHidden();
|
||||
});
|
||||
|
||||
it('tile view', async () => {
|
||||
await ensureThreeParticipants();
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p1.waitForTileViewDisplay();
|
||||
|
||||
await p1.getToolbar().clickExitTileViewButton();
|
||||
|
||||
await Promise.all([
|
||||
p1.waitForTileViewDisplay(true),
|
||||
p2.waitForTileViewDisplay(true),
|
||||
p3.waitForTileViewDisplay(true)
|
||||
]);
|
||||
|
||||
await p1.getToolbar().clickEnterTileViewButton();
|
||||
|
||||
await Promise.all([
|
||||
p1.waitForTileViewDisplay(),
|
||||
p2.waitForTileViewDisplay(),
|
||||
p3.waitForTileViewDisplay()
|
||||
]);
|
||||
});
|
||||
|
||||
it('next on stage', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().pinParticipant(p2);
|
||||
|
||||
const p2Filmstrip = p2.getFilmstrip();
|
||||
const localVideoId = await p2Filmstrip.getLocalVideoId();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
async () => await localVideoId === await p2.getLargeVideo().getId(),
|
||||
{
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'The pinned participant is not displayed on stage for p2'
|
||||
});
|
||||
|
||||
const p2VideoIdOnp3 = await p3.getFilmstrip().getRemoteVideoId(await p2.getEndpointId());
|
||||
|
||||
await p3.driver.waitUntil(
|
||||
async () => p2VideoIdOnp3 === await p3.getLargeVideo().getId(),
|
||||
{
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'The pinned participant is not displayed on stage for p3'
|
||||
});
|
||||
});
|
||||
});
|
||||
503
tests/specs/3way/lobby.spec.ts
Normal file
503
tests/specs/3way/lobby.spec.ts
Normal file
@@ -0,0 +1,503 @@
|
||||
import { P1, P3, Participant } from '../../helpers/Participant';
|
||||
import { config } from '../../helpers/TestsConfig';
|
||||
import {
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
import type { IJoinOptions } from '../../helpers/types';
|
||||
import type PreMeetingScreen from '../../pageobjects/PreMeetingScreen';
|
||||
|
||||
describe('Lobby', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureOneParticipant();
|
||||
|
||||
if (!await ctx.p1.execute(() => APP.conference._room.isLobbySupported())) {
|
||||
ctx.skipSuiteTests = true;
|
||||
}
|
||||
});
|
||||
|
||||
it('enable', async () => {
|
||||
await ensureTwoParticipants();
|
||||
|
||||
await enableLobby();
|
||||
});
|
||||
|
||||
it('entering in lobby and approve', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await enterLobby(p1, true);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p1.getNotifications().allowLobbyParticipant();
|
||||
|
||||
const notificationText = await p2.getNotifications().getLobbyParticipantAccessGranted();
|
||||
|
||||
expect(notificationText.includes(P1)).toBe(true);
|
||||
expect(notificationText.includes(P3)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyParticipantAccessGranted();
|
||||
|
||||
// ensure 3 participants in the call will check for the third one that muc is joined, ice connected,
|
||||
// media is being receiving and there are two remote streams
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());
|
||||
|
||||
expect(name).toBe(P3);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('entering in lobby and deny', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// the first time tests is executed we need to enter display name,
|
||||
// for next execution that will be locally stored
|
||||
await enterLobby(p1, false);
|
||||
|
||||
// moderator rejects access
|
||||
await p1.getNotifications().rejectLobbyParticipant();
|
||||
|
||||
// deny notification on 2nd participant
|
||||
const notificationText = await p2.getNotifications().getLobbyParticipantAccessDenied();
|
||||
|
||||
expect(notificationText.includes(P1)).toBe(true);
|
||||
expect(notificationText.includes(P3)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyParticipantAccessDenied();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
|
||||
it('approve from participants pane', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, false);
|
||||
|
||||
// moderator allows access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.admitLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// ensure 3 participants in the call will check for the third one that muc is joined, ice connected,
|
||||
// media is being receiving and there are two remote streams
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());
|
||||
|
||||
expect(name).toBe(P3);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('reject from participants pane', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, false);
|
||||
|
||||
// moderator rejects access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.rejectLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
// The third participant should see a warning that his access to the room was denied
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
// check Lobby room not left
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('lobby user leave', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
await ctx.p3.hangup();
|
||||
|
||||
// check that moderator (participant 1) no longer sees notification about participant in lobby
|
||||
await p1.getNotifications().waitForHideOfKnockingParticipants();
|
||||
});
|
||||
|
||||
it('conference ended in lobby', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
await p1.hangup();
|
||||
await p2.hangup();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.driver.$('.dialog.leaveReason').isExisting();
|
||||
|
||||
await p3.driver.waitUntil(
|
||||
async () => !await p3.getLobbyScreen().isLobbyRoomJoined(),
|
||||
{
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'p2 did not leave lobby'
|
||||
}
|
||||
);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('disable while participant in lobby', async () => {
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enableLobby();
|
||||
await enterLobby(p1);
|
||||
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
await p1SecurityDialog.toggleLobby();
|
||||
await p1SecurityDialog.waitForLobbyEnabled(true);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.waitToJoinMUC();
|
||||
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
});
|
||||
|
||||
it('change of moderators in lobby', async () => {
|
||||
// no moderator switching if jaas is available.
|
||||
if (config.iframe.usesJaas) {
|
||||
return;
|
||||
}
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// hanging up the first one, which is moderator and second one should be
|
||||
await p1.hangup();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
() => p2.isModerator(),
|
||||
{
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'p2 is not moderator after p1 leaves'
|
||||
}
|
||||
);
|
||||
|
||||
const p2SecurityDialog = p2.getSecurityDialog();
|
||||
|
||||
await p2.getToolbar().clickSecurityButton();
|
||||
await p2SecurityDialog.waitForDisplay();
|
||||
|
||||
await p2SecurityDialog.toggleLobby();
|
||||
await p2SecurityDialog.waitForLobbyEnabled();
|
||||
|
||||
// here the important check is whether the moderator sees the knocking participant
|
||||
await enterLobby(p2, false);
|
||||
});
|
||||
|
||||
it('shared password', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enableLobby();
|
||||
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
expect(await p1SecurityDialog.isLocked()).toBe(false);
|
||||
|
||||
const roomPasscode = String(Math.trunc(Math.random() * 1_000_000));
|
||||
|
||||
await p1SecurityDialog.addPassword(roomPasscode);
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
() => p1SecurityDialog.isLocked(),
|
||||
{
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'room did not lock for p1'
|
||||
}
|
||||
);
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// now fill in password
|
||||
const lobbyScreen = p3.getLobbyScreen();
|
||||
|
||||
await lobbyScreen.enterPassword(roomPasscode);
|
||||
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
});
|
||||
|
||||
it('enable with more than two participants', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureThreeParticipants();
|
||||
|
||||
await enableLobby();
|
||||
|
||||
// we need to check remote participants as isInMuc has not changed its value as
|
||||
// the bug is triggered by presence with status 322 which is not handled correctly
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p1.waitForRemoteStreams(2);
|
||||
await p2.waitForRemoteStreams(2);
|
||||
await p3.waitForRemoteStreams(2);
|
||||
});
|
||||
|
||||
it('moderator leaves while lobby enabled', async () => {
|
||||
// no moderator switching if jaas is available.
|
||||
if (config.iframe.usesJaas) {
|
||||
return;
|
||||
}
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p3.hangup();
|
||||
await p1.hangup();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
() => p2.isModerator(),
|
||||
{
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'p2 is not moderator after p1 leaves'
|
||||
}
|
||||
);
|
||||
|
||||
const lobbyScreen = p2.getLobbyScreen();
|
||||
|
||||
expect(await lobbyScreen.isLobbyRoomJoined()).toBe(true);
|
||||
});
|
||||
|
||||
it('reject and approve in pre-join', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
await enableLobby();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, true, true);
|
||||
|
||||
// moderator rejects access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.rejectLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
// The third participant should see a warning that his access to the room was denied
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
// check Lobby room left
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
// try again entering the lobby with the third one and approve it
|
||||
// check that everything is fine in the meeting
|
||||
await p3.getNotifications().closeLocalLobbyAccessDenied();
|
||||
|
||||
// let's retry to enter the lobby and approve this time
|
||||
const lobbyScreen = p3.getPreJoinScreen();
|
||||
|
||||
// click join button
|
||||
await lobbyScreen.getJoinButton().click();
|
||||
await lobbyScreen.waitToJoinLobby();
|
||||
|
||||
// check that moderator (participant 1) sees notification about participant in lobby
|
||||
const name = await p1.getNotifications().getKnockingParticipantName();
|
||||
|
||||
expect(name).toBe(P3);
|
||||
expect(await lobbyScreen.isLobbyRoomJoined()).toBe(true);
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.admitLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
await p3.waitForParticipants(2);
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
expect(await p3.getFilmstrip().countVisibleThumbnails()).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Enable lobby and check that it is enabled.
|
||||
*/
|
||||
async function enableLobby() {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
expect(await p1SecurityDialog.isLobbyEnabled()).toBe(false);
|
||||
|
||||
await p1SecurityDialog.toggleLobby();
|
||||
await p1SecurityDialog.waitForLobbyEnabled();
|
||||
|
||||
expect((await p2.getNotifications().getLobbyEnabledText()).includes(p1.name)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyEnabled();
|
||||
|
||||
const p2SecurityDialog = p2.getSecurityDialog();
|
||||
|
||||
await p2.getToolbar().clickSecurityButton();
|
||||
await p2SecurityDialog.waitForDisplay();
|
||||
|
||||
// lobby is visible to moderators only, this depends on whether deployment is all moderators or not
|
||||
if (await p2.isModerator()) {
|
||||
await p2SecurityDialog.waitForLobbyEnabled();
|
||||
} else {
|
||||
expect(await p2SecurityDialog.isLobbySectionPresent()).toBe(false);
|
||||
}
|
||||
|
||||
// let's close the security dialog, or we will not be able to click
|
||||
// on popups for allow/deny participants
|
||||
await p1SecurityDialog.clickCloseButton();
|
||||
await p2SecurityDialog.clickCloseButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects that lobby is enabled for the room we will try to join.
|
||||
* Lobby UI is shown, enter display name and join.
|
||||
* Checks Lobby UI and also that when joining the moderator sees corresponding notifications.
|
||||
*
|
||||
* @param participant The participant that is moderator in the meeting.
|
||||
* @param enterDisplayName whether to enter display name. We need to enter display name only the first time when
|
||||
* a participant sees the lobby screen, next time visiting the page display name will be pre-filled
|
||||
* from local storage.
|
||||
* @param usePreJoin
|
||||
* @return the participant name knocking.
|
||||
*/
|
||||
async function enterLobby(participant: Participant, enterDisplayName = false, usePreJoin = false) {
|
||||
const options: IJoinOptions = { };
|
||||
|
||||
if (usePreJoin) {
|
||||
options.configOverwrite = {
|
||||
prejoinConfig: {
|
||||
enabled: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
await ensureThreeParticipants({
|
||||
...options,
|
||||
skipDisplayName: true,
|
||||
skipWaitToJoin: true,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
let screen: PreMeetingScreen;
|
||||
|
||||
// WebParticipant participant3 = getParticipant3();
|
||||
// ParentPreMeetingScreen lobbyScreen;
|
||||
if (usePreJoin) {
|
||||
screen = p3.getPreJoinScreen();
|
||||
} else {
|
||||
screen = p3.getLobbyScreen();
|
||||
}
|
||||
|
||||
// participant 3 should be now on pre-join screen
|
||||
await screen.waitForLoading();
|
||||
|
||||
const displayNameInput = screen.getDisplayNameInput();
|
||||
|
||||
// check display name is visible
|
||||
expect(await displayNameInput.isExisting()).toBe(true);
|
||||
expect(await displayNameInput.isDisplayed()).toBe(true);
|
||||
|
||||
const joinButton = screen.getJoinButton();
|
||||
|
||||
expect(await joinButton.isExisting()).toBe(true);
|
||||
|
||||
if (enterDisplayName) {
|
||||
let classes = await joinButton.getAttribute('class');
|
||||
|
||||
if (!usePreJoin) {
|
||||
// check join button is disabled
|
||||
expect(classes.includes('disabled')).toBe(true);
|
||||
}
|
||||
|
||||
// TODO check that password is hidden as the room does not have password
|
||||
// this check needs to be added once the functionality exists
|
||||
|
||||
// enter display name
|
||||
await screen.enterDisplayName(P3);
|
||||
|
||||
// check join button is enabled
|
||||
classes = await joinButton.getAttribute('class');
|
||||
expect(classes.includes('disabled')).toBe(false);
|
||||
}
|
||||
|
||||
// click join button
|
||||
await screen.getJoinButton().click();
|
||||
await screen.waitToJoinLobby();
|
||||
|
||||
// check no join button
|
||||
await p3.driver.waitUntil(
|
||||
async () => !await joinButton.isExisting() || !await joinButton.isDisplayed() || !await joinButton.isEnabled(),
|
||||
{
|
||||
timeout: 2_000,
|
||||
timeoutMsg: 'Join button is still available for p3'
|
||||
});
|
||||
|
||||
// new screen, is password button shown
|
||||
const passwordButton = screen.getPasswordButton();
|
||||
|
||||
expect(await passwordButton.isExisting()).toBe(true);
|
||||
expect(await passwordButton.isEnabled()).toBe(true);
|
||||
|
||||
// check that moderator (participant 1) sees notification about participant in lobby
|
||||
const name = await participant.getNotifications().getKnockingParticipantName();
|
||||
|
||||
expect(name).toBe(P3);
|
||||
expect(await screen.isLobbyRoomJoined()).toBe(true);
|
||||
|
||||
return name;
|
||||
}
|
||||
81
tests/specs/3way/oneOnOne.spec.ts
Normal file
81
tests/specs/3way/oneOnOne.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import {
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants
|
||||
} from '../../helpers/participants';
|
||||
|
||||
const ONE_ON_ONE_CONFIG_OVERRIDES = {
|
||||
configOverwrite: {
|
||||
disable1On1Mode: false,
|
||||
toolbarConfig: {
|
||||
timeout: 500,
|
||||
alwaysVisible: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('OneOnOne', () => {
|
||||
it('filmstrip hidden in 1on1', async () => {
|
||||
await ensureTwoParticipants(ONE_ON_ONE_CONFIG_OVERRIDES);
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await configureToolbarsToHideQuickly(p1);
|
||||
await configureToolbarsToHideQuickly(p2);
|
||||
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(false);
|
||||
await p2.getFilmstrip().verifyRemoteVideosDisplay(false);
|
||||
});
|
||||
|
||||
it('filmstrip visible with more than 2', async () => {
|
||||
await ensureThreeParticipants(ONE_ON_ONE_CONFIG_OVERRIDES);
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await configureToolbarsToHideQuickly(p3);
|
||||
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
await p2.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
await p3.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
});
|
||||
|
||||
it('filmstrip display when returning to 1on1', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p2.getFilmstrip().pinParticipant(p2);
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(false);
|
||||
await p2.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
});
|
||||
|
||||
it('filmstrip visible on self view focus', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().pinParticipant(p1);
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
|
||||
await p1.getFilmstrip().unpinParticipant(p1);
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(false);
|
||||
});
|
||||
|
||||
it('filmstrip hover show videos', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().hoverOverLocalVideo();
|
||||
|
||||
await p1.getFilmstrip().verifyRemoteVideosDisplay(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Hangs up all participants (p1, p2 and p3)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function configureToolbarsToHideQuickly(participant: Participant): Promise<void> {
|
||||
return participant.execute(() => {
|
||||
APP.UI.dockToolbar(false);
|
||||
APP.UI.showToolbar(250);
|
||||
});
|
||||
}
|
||||
290
tests/specs/3way/startMuted.spec.ts
Normal file
290
tests/specs/3way/startMuted.spec.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import {
|
||||
checkForScreensharingTile,
|
||||
ensureOneParticipant,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants,
|
||||
joinSecondParticipant,
|
||||
joinThirdParticipant,
|
||||
unmuteVideoAndCheck
|
||||
} from '../../helpers/participants';
|
||||
|
||||
describe('StartMuted', () => {
|
||||
it('checkboxes test', async () => {
|
||||
const options = {
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: true
|
||||
},
|
||||
testing: {
|
||||
testMode: true,
|
||||
debugAudioLevels: true
|
||||
}
|
||||
} };
|
||||
|
||||
await ensureOneParticipant(options);
|
||||
|
||||
const { p1 } = ctx;
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
|
||||
await p1.getToolbar().clickSettingsButton();
|
||||
|
||||
const settingsDialog = p1.getSettingsDialog();
|
||||
|
||||
await settingsDialog.waitForDisplay();
|
||||
|
||||
await settingsDialog.setStartAudioMuted(true);
|
||||
await settingsDialog.setStartVideoMuted(true);
|
||||
await settingsDialog.submit();
|
||||
|
||||
// Check that p1 doesn't get muted.
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p1, true);
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, true);
|
||||
|
||||
await joinSecondParticipant({
|
||||
...options,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
// Enable screenshare on p1.
|
||||
p1.getToolbar().clickDesktopSharingButton();
|
||||
await checkForScreensharingTile(p1, p1);
|
||||
|
||||
const { p2 } = ctx;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
await p2.waitForIceConnected();
|
||||
await p2.waitForReceiveMedia();
|
||||
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
await p1.waitForAudioMuted(p2, true);
|
||||
await p1.waitForRemoteVideo(p2EndpointId, true);
|
||||
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p1, true);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, true);
|
||||
await p2.waitForAudioMuted(p1, false);
|
||||
await p2.waitForRemoteVideo(p1EndpointId, false);
|
||||
|
||||
// Check if a remote screenshare tile is created on p2.
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
|
||||
// Enable video on p2 and check if p2 appears unmuted on p1.
|
||||
await Promise.all([
|
||||
p2.getToolbar().clickAudioUnmuteButton(), p2.getToolbar().clickVideoUnmuteButton()
|
||||
]);
|
||||
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2, true);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2, true);
|
||||
|
||||
await p1.waitForAudioMuted(p2, false);
|
||||
await p1.waitForRemoteVideo(p2EndpointId, false);
|
||||
|
||||
// Add a third participant and check p3 is able to receive audio and video from p2.
|
||||
await joinThirdParticipant({
|
||||
...options,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForReceiveMedia();
|
||||
|
||||
await p3.getFilmstrip().assertAudioMuteIconIsDisplayed(p2, true);
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2, true);
|
||||
await checkForScreensharingTile(p1, p3);
|
||||
});
|
||||
|
||||
it('config options test', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
const options = {
|
||||
configOverwrite: {
|
||||
testing: {
|
||||
testMode: true,
|
||||
debugAudioLevels: true
|
||||
},
|
||||
startAudioMuted: 2,
|
||||
startVideoMuted: 2
|
||||
}
|
||||
};
|
||||
|
||||
await ensureOneParticipant(options);
|
||||
await joinSecondParticipant({
|
||||
...options,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p2.waitForIceConnected();
|
||||
await p2.waitForReceiveMedia();
|
||||
|
||||
await joinThirdParticipant({
|
||||
...options,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForReceiveMedia();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
const p2ID = await p2.getEndpointId();
|
||||
|
||||
p1.log(`Start configOptionsTest, second participant: ${p2ID}`);
|
||||
|
||||
// Participant 3 should be muted, 1 and 2 unmuted.
|
||||
await p3.getFilmstrip().assertAudioMuteIconIsDisplayed(p3);
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p3);
|
||||
|
||||
await Promise.all([
|
||||
p1.waitForAudioMuted(p3, true),
|
||||
p2.waitForAudioMuted(p3, true)
|
||||
]);
|
||||
|
||||
await p3.getFilmstrip().assertAudioMuteIconIsDisplayed(p1, true);
|
||||
await p3.getFilmstrip().assertAudioMuteIconIsDisplayed(p2, true);
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, true);
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2, true);
|
||||
|
||||
// Unmute and see if the audio works
|
||||
await p3.getToolbar().clickAudioUnmuteButton();
|
||||
p1.log('configOptionsTest, unmuted third participant');
|
||||
await p1.waitForAudioMuted(p3, false /* unmuted */);
|
||||
});
|
||||
|
||||
it('startWithVideoMuted=true can unmute', async () => {
|
||||
// Maybe disable if there is FF or Safari participant.
|
||||
|
||||
await hangupAllParticipants();
|
||||
|
||||
// Explicitly enable P2P due to a regression with unmute not updating
|
||||
// large video while in P2P.
|
||||
const options = {
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: true
|
||||
},
|
||||
startWithVideoMuted: true
|
||||
}
|
||||
};
|
||||
|
||||
await ensureTwoParticipants(options);
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
await Promise.all([
|
||||
p1.getLargeVideo().waitForSwitchTo(await p2.getEndpointId()),
|
||||
p2.getLargeVideo().waitForSwitchTo(await p1.getEndpointId())
|
||||
]);
|
||||
|
||||
await unmuteVideoAndCheck(p2, p1);
|
||||
await p1.getLargeVideo().assertPlaying();
|
||||
});
|
||||
|
||||
it('startWithAudioMuted=true can unmute', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
const options = {
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
testing: {
|
||||
testMode: true,
|
||||
debugAudioLevels: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await ensureTwoParticipants(options);
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await Promise.all([ p1.waitForAudioMuted(p2, true), p2.waitForAudioMuted(p1, true) ]);
|
||||
await p1.getToolbar().clickAudioUnmuteButton();
|
||||
await Promise.all([ p1.waitForAudioMuted(p2, true), p2.waitForAudioMuted(p1, false) ]);
|
||||
});
|
||||
|
||||
it('startWithAudioVideoMuted=true can unmute', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
const options = {
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: true,
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await ensureOneParticipant(options);
|
||||
await joinSecondParticipant({
|
||||
configOverwrite: {
|
||||
testing: {
|
||||
testMode: true,
|
||||
debugAudioLevels: true
|
||||
},
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p2.waitForIceConnected();
|
||||
await p2.waitForSendMedia();
|
||||
|
||||
await p2.waitForAudioMuted(p1, true);
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
// Unmute p1's both audio and video and check on p2.
|
||||
await p1.getToolbar().clickAudioUnmuteButton();
|
||||
await p2.waitForAudioMuted(p1, false);
|
||||
|
||||
await unmuteVideoAndCheck(p1, p2);
|
||||
await p2.getLargeVideo().assertPlaying();
|
||||
});
|
||||
|
||||
|
||||
it('test p2p JVB switch and switch back', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// Mute p2's video just before p3 joins.
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await joinThirdParticipant({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// Unmute p2 and check if its video is being received by p1 and p3.
|
||||
await unmuteVideoAndCheck(p2, p3);
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2, true);
|
||||
|
||||
// Mute p2's video just before p3 leaves.
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2);
|
||||
|
||||
await p2.getToolbar().clickVideoUnmuteButton();
|
||||
|
||||
// Check if p2's video is playing on p1.
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p2, true);
|
||||
await p1.getLargeVideo().assertPlaying();
|
||||
});
|
||||
});
|
||||
113
tests/specs/3way/tileView.spec.ts
Normal file
113
tests/specs/3way/tileView.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
/**
|
||||
* The CSS selector for local video when outside of tile view. It should
|
||||
* be in a container separate from remote videos so remote videos can
|
||||
* scroll while local video stays docked.
|
||||
*/
|
||||
const FILMSTRIP_VIEW_LOCAL_VIDEO_CSS_SELECTOR = '#filmstripLocalVideo #localVideoContainer';
|
||||
|
||||
/**
|
||||
* The CSS selector for local video tile view is enabled. It should display
|
||||
* at the end of all the other remote videos, as the last tile.
|
||||
*/
|
||||
const TILE_VIEW_LOCAL_VIDEO_CSS_SELECTOR = '.remote-videos #localVideoContainer';
|
||||
|
||||
describe('TileView', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants());
|
||||
|
||||
// TODO: implements etherpad check
|
||||
|
||||
it('pinning exits', async () => {
|
||||
await enterTileView();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().pinParticipant(p2);
|
||||
|
||||
await p1.waitForTileViewDisplay(true);
|
||||
});
|
||||
|
||||
it('local video display', async () => {
|
||||
await enterTileView();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.driver.$(TILE_VIEW_LOCAL_VIDEO_CSS_SELECTOR).waitForDisplayed({ timeout: 3000 });
|
||||
await p1.driver.$(FILMSTRIP_VIEW_LOCAL_VIDEO_CSS_SELECTOR).waitForDisplayed({
|
||||
timeout: 3000,
|
||||
reverse: true
|
||||
});
|
||||
});
|
||||
|
||||
it('can exit', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getToolbar().clickExitTileViewButton();
|
||||
await p1.waitForTileViewDisplay(true);
|
||||
});
|
||||
|
||||
it('local video display independently from remote', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.driver.$(TILE_VIEW_LOCAL_VIDEO_CSS_SELECTOR).waitForDisplayed({
|
||||
timeout: 3000,
|
||||
reverse: true
|
||||
});
|
||||
await p1.driver.$(FILMSTRIP_VIEW_LOCAL_VIDEO_CSS_SELECTOR).waitForDisplayed({ timeout: 3000 });
|
||||
});
|
||||
|
||||
it('lastN', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
if (p1.driver.isFirefox) {
|
||||
// Firefox does not support external audio file as input.
|
||||
// Not testing as second participant cannot be dominant speaker.
|
||||
return;
|
||||
}
|
||||
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
channelLastN: 1,
|
||||
startWithAudioMuted: true
|
||||
}
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// one inactive icon should appear in few seconds
|
||||
await p3.waitForNinjaIcon();
|
||||
|
||||
const p1EpId = await p1.getEndpointId();
|
||||
|
||||
await p3.waitForRemoteVideo(p1EpId);
|
||||
|
||||
const p2EpId = await p2.getEndpointId();
|
||||
|
||||
await p3.waitForNinjaIcon(p2EpId);
|
||||
|
||||
// no video for participant 2
|
||||
await p3.waitForRemoteVideo(p2EpId, true);
|
||||
|
||||
// mute audio for participant 1
|
||||
await p1.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// unmute audio for participant 2
|
||||
await p2.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
await p3.waitForDominantSpeaker(p2EpId);
|
||||
|
||||
// check video of participant 2 should be received
|
||||
await p3.waitForRemoteVideo(p2EpId);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Attempts to enter tile view and verifies tile view has been entered.
|
||||
*/
|
||||
async function enterTileView() {
|
||||
await ctx.p1.getToolbar().clickEnterTileViewButton();
|
||||
await ctx.p1.waitForTileViewDisplay();
|
||||
}
|
||||
Reference in New Issue
Block a user