snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
479
tests/specs/media/activeSpeaker.spec.ts
Normal file
479
tests/specs/media/activeSpeaker.spec.ts
Normal file
@@ -0,0 +1,479 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import {
|
||||
checkForScreensharingTile,
|
||||
ensureSixParticipants,
|
||||
ensureThreeParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
import { muteAudioAndCheck } from '../helpers/mute';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3', 'p4', 'p5', 'p6' ]
|
||||
});
|
||||
|
||||
describe('Active speaker', () => {
|
||||
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);
|
||||
|
||||
await hangupAllParticipants();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that the dominant speaker appears in the filmstrip in stage view
|
||||
* even when alphabetically last with limited visible slots.
|
||||
* This tests the fix for the bug where dominant speakers at the bottom of
|
||||
* the alphabetically sorted list would not appear when slots were limited.
|
||||
*
|
||||
* Note: This test verifies filmstrip ordering via Redux state
|
||||
* (visibleRemoteParticipants), not large video behavior.
|
||||
*/
|
||||
it.skip('testDominantSpeakerInFilmstripWithLimitedSlots', async () => {
|
||||
await ensureSixParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true
|
||||
}
|
||||
});
|
||||
const { p1, p2, p3, p4, p5, p6 } = ctx;
|
||||
|
||||
// Resize p1's window to limit filmstrip slots to 2-3 tiles
|
||||
// This creates the condition where not all participants fit in the filmstrip
|
||||
await p1.driver.setWindowSize(1024, 600);
|
||||
await p1.driver.pause(1000); // Wait for layout to adjust
|
||||
|
||||
// Set display names to create alphabetical ordering
|
||||
// Names chosen so p6 ("Zoe") is alphabetically last: Alice, Bob, Charlie, Eve, Frank, Zoe
|
||||
await setAlphabeticalDisplayNames(p1, p2, p3, p4, p5, p6);
|
||||
|
||||
// Test with multiple speakers: Eve (p4), Frank (p5), and Zoe (p6)
|
||||
// This verifies the fix works for different alphabetical positions
|
||||
const speakersToTest = [
|
||||
{ participant: p4, name: 'Eve' },
|
||||
{ participant: p5, name: 'Frank' },
|
||||
{ participant: p6, name: 'Zoe' }
|
||||
];
|
||||
|
||||
for (const { participant, name } of speakersToTest) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await p1.log(`Testing ${name} (${participantId}) as dominant speaker`);
|
||||
|
||||
// Make this participant the dominant speaker by unmuting
|
||||
await participant.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// Wait for the dominant speaker to be detected
|
||||
await waitForDominantSpeaker(p1, participantId, name);
|
||||
|
||||
// Verify that the participant appears in the visible remote participants
|
||||
const filmstripState = await getFilmstripState(p1);
|
||||
|
||||
await p1.log(`Dominant speaker: ${filmstripState.dominantSpeaker}`);
|
||||
await p1.log(`Visible remote participants: ${JSON.stringify(filmstripState.visibleRemoteParticipants)}`);
|
||||
await p1.log(`${name} endpoint ID: ${participantId}`);
|
||||
await p1.log(`Total remote: ${filmstripState.remoteParticipants.length}, Visible: ${filmstripState.visibleRemoteParticipants.length}`);
|
||||
|
||||
// Verify we actually have slot limitation (fewer visible than total)
|
||||
expect(filmstripState.visibleRemoteParticipants.length).toBeLessThan(filmstripState.remoteParticipants.length);
|
||||
|
||||
// Assert that the dominant speaker is in the visible participants
|
||||
// This is the key test - even though they may be alphabetically late and slots are limited,
|
||||
// they should still be visible because the fix reserves a slot for dominant speaker
|
||||
expect(filmstripState.visibleRemoteParticipants).toContain(participantId);
|
||||
|
||||
// Verify the dominant speaker thumbnail is visible in the filmstrip
|
||||
await p1.driver.$(`//span[@id='participant_${participantId}']`).waitForDisplayed({
|
||||
timeout: 5_000,
|
||||
timeoutMsg: `${name} dominant speaker thumbnail not visible in filmstrip`
|
||||
});
|
||||
|
||||
// Mute this participant back before testing the next one
|
||||
await participant.getToolbar().clickAudioMuteButton();
|
||||
await p1.driver.pause(2000);
|
||||
}
|
||||
|
||||
await hangupAllParticipants();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test dominant speaker in filmstrip with screensharing active.
|
||||
* Verifies that dominant speaker is still visible when screen shares
|
||||
* take up some of the visible slots.
|
||||
*/
|
||||
it.skip('testDominantSpeakerWithScreensharing', async () => {
|
||||
await ensureSixParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true
|
||||
}
|
||||
});
|
||||
const { p1, p2, p3, p4, p5, p6 } = ctx;
|
||||
|
||||
// Resize p1's window to limit filmstrip slots
|
||||
await p1.driver.setWindowSize(1024, 600);
|
||||
await p1.driver.pause(1000); // Wait for layout to adjust
|
||||
|
||||
// Set display names
|
||||
await setAlphabeticalDisplayNames(p1, p2, p3, p4, p5, p6);
|
||||
|
||||
// Start screensharing from p2
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
|
||||
// Test with multiple speakers while screensharing is active
|
||||
const speakersToTest = [
|
||||
{ participant: p4, name: 'Eve' },
|
||||
{ participant: p5, name: 'Frank' },
|
||||
{ participant: p6, name: 'Zoe' }
|
||||
];
|
||||
|
||||
for (const { participant, name } of speakersToTest) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await p1.log(`Testing ${name} (${participantId}) as dominant speaker with screensharing`);
|
||||
|
||||
// Make this participant the dominant speaker by unmuting
|
||||
await participant.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// Wait for the dominant speaker to be detected
|
||||
await waitForDominantSpeaker(p1, participantId, name);
|
||||
|
||||
// Verify dominant speaker is still visible in filmstrip despite screenshare
|
||||
const filmstripState = await getFilmstripState(p1);
|
||||
|
||||
await p1.log(`Dominant speaker (with screenshare): ${filmstripState.dominantSpeaker}`);
|
||||
await p1.log(`Visible remote participants with screenshare: ${JSON.stringify(filmstripState.visibleRemoteParticipants)}`);
|
||||
await p1.log(`${name} endpoint ID: ${participantId}`);
|
||||
await p1.log(`Total remote: ${filmstripState.remoteParticipants.length}, Visible: ${filmstripState.visibleRemoteParticipants.length}`);
|
||||
|
||||
// Verify we have slot limitation even with screensharing
|
||||
expect(filmstripState.visibleRemoteParticipants.length).toBeLessThan(filmstripState.remoteParticipants.length);
|
||||
|
||||
// The dominant speaker should still be in the visible participants despite screenshare taking slots
|
||||
expect(filmstripState.visibleRemoteParticipants).toContain(participantId);
|
||||
|
||||
// Verify thumbnail visibility
|
||||
await p1.driver.$(`//span[@id='participant_${participantId}']`).waitForDisplayed({
|
||||
timeout: 5_000,
|
||||
timeoutMsg: `${name} not visible with screensharing active`
|
||||
});
|
||||
|
||||
// Mute this participant back before testing the next one
|
||||
await participant.getToolbar().clickAudioMuteButton();
|
||||
await p1.driver.pause(1000);
|
||||
}
|
||||
|
||||
// Clean up - stop screensharing
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
await hangupAllParticipants();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test that filmstrip maintains stable ordering when multiple speakers alternate.
|
||||
* Verifies that the alphabetical sorting prevents visual reordering when the same
|
||||
* set of speakers take turns speaking.
|
||||
*/
|
||||
it.skip('testFilmstripStableOrderingWithMultipleSpeakers', async () => {
|
||||
await ensureSixParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true
|
||||
}
|
||||
});
|
||||
const { p1, p2, p3, p4, p5, p6 } = ctx;
|
||||
|
||||
// Resize p1's window to limit filmstrip slots
|
||||
await p1.driver.setWindowSize(1024, 600);
|
||||
await p1.driver.pause(1000); // Wait for layout to adjust
|
||||
|
||||
// Set display names
|
||||
await setAlphabeticalDisplayNames(p1, p2, p3, p4, p5, p6);
|
||||
|
||||
// First, have Eve, Frank, and Zoe all speak to get them into the active speakers list
|
||||
const speakersToTest = [
|
||||
{ participant: p4, name: 'Eve' },
|
||||
{ participant: p5, name: 'Frank' },
|
||||
{ participant: p6, name: 'Zoe' }
|
||||
];
|
||||
|
||||
await p1.log('Initial round: getting all three speakers into active speakers list');
|
||||
|
||||
for (const { participant, name } of speakersToTest) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await p1.log(`${name} (${participantId}) speaking for the first time`);
|
||||
await participant.getToolbar().clickAudioUnmuteButton();
|
||||
await waitForDominantSpeaker(p1, participantId, name);
|
||||
await participant.getToolbar().clickAudioMuteButton();
|
||||
await p1.driver.pause(1000);
|
||||
}
|
||||
|
||||
// Now cycle through them again and verify they maintain alphabetical order (Eve, Frank, Zoe)
|
||||
await p1.log('Second round: verifying stable alphabetical ordering when speakers alternate');
|
||||
|
||||
const states = [];
|
||||
|
||||
for (const { participant, name } of speakersToTest) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await p1.log(`Testing ${name} (${participantId}) for stable ordering`);
|
||||
|
||||
// Make this participant the dominant speaker
|
||||
await participant.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// Wait for the dominant speaker to be detected
|
||||
await waitForDominantSpeaker(p1, participantId, name);
|
||||
|
||||
// Capture filmstrip state
|
||||
const filmstripState = await getFilmstripState(p1);
|
||||
|
||||
states.push({ name, id: participantId, state: filmstripState });
|
||||
|
||||
// Mute back
|
||||
await participant.getToolbar().clickAudioMuteButton();
|
||||
await p1.driver.pause(1000);
|
||||
}
|
||||
|
||||
const [ eveState, frankState, zoeState ] = states;
|
||||
|
||||
// Helper function to get participant names in the order they appear
|
||||
const getVisibleParticipantNames = async (visibleIds: string[]) => {
|
||||
return await p1.execute(ids => {
|
||||
const state = APP.store.getState();
|
||||
const participants = state['features/base/participants'];
|
||||
|
||||
return ids.map(id => {
|
||||
const participant = participants.remote.get(id);
|
||||
|
||||
return participant?.name || 'Unknown';
|
||||
});
|
||||
}, visibleIds);
|
||||
};
|
||||
|
||||
// Get the names of visible participants for each state
|
||||
const eveVisibleNames = await getVisibleParticipantNames(eveState.state.visibleRemoteParticipants);
|
||||
const frankVisibleNames = await getVisibleParticipantNames(frankState.state.visibleRemoteParticipants);
|
||||
const zoeVisibleNames = await getVisibleParticipantNames(zoeState.state.visibleRemoteParticipants);
|
||||
|
||||
await p1.log(`Visible participants when Eve is dominant: ${JSON.stringify(eveVisibleNames)}`);
|
||||
await p1.log(`Visible participants when Frank is dominant: ${JSON.stringify(frankVisibleNames)}`);
|
||||
await p1.log(`Visible participants when Zoe is dominant: ${JSON.stringify(zoeVisibleNames)}`);
|
||||
|
||||
await p1.log(`Eve visible count: ${eveState.state.visibleRemoteParticipants.length}, total remote: ${eveState.state.remoteParticipants.length}`);
|
||||
await p1.log(`Frank visible count: ${frankState.state.visibleRemoteParticipants.length}, total remote: ${frankState.state.remoteParticipants.length}`);
|
||||
await p1.log(`Zoe visible count: ${zoeState.state.visibleRemoteParticipants.length}, total remote: ${zoeState.state.remoteParticipants.length}`);
|
||||
|
||||
// Verify that each dominant speaker appears in visible participants
|
||||
expect(eveState.state.visibleRemoteParticipants).toContain(eveState.id);
|
||||
expect(frankState.state.visibleRemoteParticipants).toContain(frankState.id);
|
||||
expect(zoeState.state.visibleRemoteParticipants).toContain(zoeState.id);
|
||||
|
||||
// Helper function to get the relative order of Eve, Frank, and Zoe
|
||||
const getSpeakersOrder = (names: string[]) => {
|
||||
return names.filter(n => [ 'Eve', 'Frank', 'Zoe' ].includes(n));
|
||||
};
|
||||
|
||||
const eveOrder = getSpeakersOrder(eveVisibleNames);
|
||||
const frankOrder = getSpeakersOrder(frankVisibleNames);
|
||||
const zoeOrder = getSpeakersOrder(zoeVisibleNames);
|
||||
|
||||
await p1.log(`Speakers order when Eve is dominant: ${JSON.stringify(eveOrder)}`);
|
||||
await p1.log(`Speakers order when Frank is dominant: ${JSON.stringify(frankOrder)}`);
|
||||
await p1.log(`Speakers order when Zoe is dominant: ${JSON.stringify(zoeOrder)}`);
|
||||
|
||||
// Verify that the dominant speaker is always in the visible list (this tests the bug fix)
|
||||
expect(eveOrder).toContain('Eve');
|
||||
expect(frankOrder).toContain('Frank');
|
||||
expect(zoeOrder).toContain('Zoe');
|
||||
|
||||
// Helper to check if array is alphabetically sorted
|
||||
const isAlphabeticallySorted = (names: string[]) => {
|
||||
for (let i = 0; i < names.length - 1; i++) {
|
||||
if (names[i].localeCompare(names[i + 1]) > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Verify that whatever speakers ARE visible maintain alphabetical order
|
||||
// This is the key test - when the same speakers alternate, visible speakers stay in alphabetical order
|
||||
expect(isAlphabeticallySorted(eveOrder)).toBe(true);
|
||||
expect(isAlphabeticallySorted(frankOrder)).toBe(true);
|
||||
expect(isAlphabeticallySorted(zoeOrder)).toBe(true);
|
||||
|
||||
// Additionally verify order consistency: if multiple speakers are visible in multiple states,
|
||||
// their relative order should be the same
|
||||
// For example, if Eve and Frank are both visible when Zoe speaks, they should be [Eve, Frank]
|
||||
if (eveOrder.includes('Frank') && frankOrder.includes('Eve')) {
|
||||
// Both Eve and Frank visible in both states
|
||||
const eveAndFrankInEveState = eveOrder.filter(n => [ 'Eve', 'Frank' ].includes(n));
|
||||
const eveAndFrankInFrankState = frankOrder.filter(n => [ 'Eve', 'Frank' ].includes(n));
|
||||
|
||||
expect(eveAndFrankInEveState).toEqual(eveAndFrankInFrankState);
|
||||
}
|
||||
|
||||
await p1.log('Filmstrip maintains alphabetical ordering of visible speakers when dominant speaker changes');
|
||||
|
||||
await hangupAllParticipants();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
await activeSpeaker.log(`Start testActiveSpeaker for participant: ${activeSpeaker.name}`);
|
||||
|
||||
const speakerEndpoint = await activeSpeaker.getEndpointId();
|
||||
|
||||
// just a debug print to go in logs
|
||||
await activeSpeaker.log('Unmuting in testActiveSpeaker');
|
||||
|
||||
// Unmute
|
||||
await activeSpeaker.getToolbar().clickAudioUnmuteButton();
|
||||
|
||||
// just a debug print to go in logs
|
||||
await otherParticipant1.log(`Participant unmuted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
await 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
|
||||
await activeSpeaker.log('Muting in testActiveSpeaker');
|
||||
|
||||
// Mute back again
|
||||
await activeSpeaker.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// just a debug print to go in logs
|
||||
await otherParticipant1.log(`Participant muted in testActiveSpeaker ${speakerEndpoint}`);
|
||||
await 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a participant to be detected as the dominant speaker.
|
||||
*
|
||||
* @param {Participant} observer - The participant observing the dominant speaker state.
|
||||
* @param {string} participantId - The endpoint ID of the expected dominant speaker.
|
||||
* @param {string} participantName - The name of the participant for logging.
|
||||
*/
|
||||
async function waitForDominantSpeaker(
|
||||
observer: Participant,
|
||||
participantId: string,
|
||||
participantName: string
|
||||
): Promise<void> {
|
||||
await observer.driver.waitUntil(
|
||||
async () => {
|
||||
const state = await observer.execute(() => {
|
||||
const participants = APP.store.getState()['features/base/participants'];
|
||||
|
||||
return participants.dominantSpeaker;
|
||||
});
|
||||
|
||||
return state === participantId;
|
||||
},
|
||||
{
|
||||
timeout: 10000,
|
||||
timeoutMsg: `${participantName} (${participantId}) was not detected as dominant speaker within 10 seconds`
|
||||
}
|
||||
);
|
||||
|
||||
// Wait a bit more for filmstrip state to update after dominant speaker changes
|
||||
await observer.driver.pause(1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current filmstrip state from Redux.
|
||||
*
|
||||
* @param {Participant} participant - The participant to query.
|
||||
* @returns {Promise<FilmstripState>} The filmstrip state.
|
||||
*/
|
||||
async function getFilmstripState(participant: Participant): Promise<{
|
||||
dominantSpeaker: string | null;
|
||||
remoteParticipants: string[];
|
||||
visibleRemoteParticipants: string[];
|
||||
}> {
|
||||
return await participant.execute(() => {
|
||||
const state = APP.store.getState();
|
||||
const filmstrip = state['features/filmstrip'];
|
||||
const participants = state['features/base/participants'];
|
||||
|
||||
return {
|
||||
dominantSpeaker: participants.dominantSpeaker,
|
||||
remoteParticipants: filmstrip.remoteParticipants,
|
||||
visibleRemoteParticipants: Array.from(filmstrip.visibleRemoteParticipants)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set display names for all 6 participants to create alphabetical ordering.
|
||||
*/
|
||||
async function setAlphabeticalDisplayNames(
|
||||
p1: Participant,
|
||||
p2: Participant,
|
||||
p3: Participant,
|
||||
p4: Participant,
|
||||
p5: Participant,
|
||||
p6: Participant
|
||||
): Promise<void> {
|
||||
await p1.setLocalDisplayName('Alice');
|
||||
await p2.setLocalDisplayName('Bob');
|
||||
await p3.setLocalDisplayName('Charlie');
|
||||
await p4.setLocalDisplayName('Eve');
|
||||
await p5.setLocalDisplayName('Frank');
|
||||
await p6.setLocalDisplayName('Zoe');
|
||||
|
||||
// Wait for display names to propagate
|
||||
await p1.driver.pause(2000);
|
||||
}
|
||||
336
tests/specs/media/audioVideoModeration.spec.ts
Normal file
336
tests/specs/media/audioVideoModeration.spec.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
import { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { expectations } from '../../helpers/expectations';
|
||||
import {
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants, ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
import { unmuteAudioAndCheck, unmuteVideoAndCheck } from '../helpers/mute';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3' ]
|
||||
});
|
||||
|
||||
describe('Audio/video moderation', () => {
|
||||
|
||||
it('setup', async () => {
|
||||
await ensureThreeParticipants();
|
||||
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
// if all 3 participants are moderators, skip this test
|
||||
if (!await p1.isModerator()
|
||||
|| (await p1.isModerator() && await p2.isModerator() && await p3.isModerator())) {
|
||||
ctx.skipSuiteTests = `Unsupported moderator configuration: p1=${await p1.isModerator()},\
|
||||
p2=${await p2.isModerator()}, p3=${await p3.isModerator()}`;
|
||||
}
|
||||
});
|
||||
|
||||
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 () => {
|
||||
// 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 (!expectations.autoModerator) {
|
||||
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.askToUnmute(nonModerator, false);
|
||||
await nonModerator.getNotifications().waitForAskToUnmuteNotification();
|
||||
await unmuteAudioAndCheck(nonModerator, p1);
|
||||
|
||||
await moderatorParticipantsPane.allowVideo(nonModerator);
|
||||
await nonModerator.getNotifications().waitForAskToUnmuteNotification();
|
||||
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);
|
||||
|
||||
// close and open participants pane to make sure the context menu disappears
|
||||
await p1.getParticipantsPane().close();
|
||||
await p1.getParticipantsPane().open();
|
||||
|
||||
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);
|
||||
});
|
||||
it('mute a participant that reports muted', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
// Enable moderation and let p2 unmute. This puts p2 on the whitelist.
|
||||
await unmuteByModerator(p1, p2, true, false);
|
||||
|
||||
// p2 mutes itself. The mute state that p2 reports is not reliable, thus p1 must keep the mute action. The
|
||||
// mute action is the action that removes p2 from the whitelist and mutes p2 on the bridge.
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
|
||||
await p1ParticipantsPane.waitForMuteAudioAction(p2);
|
||||
await p1ParticipantsPane.muteAudio(p2);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
// p2 is not on the whitelist any more, thus p2 cannot unmute.
|
||||
await tryToAudioUnmuteAndCheck(p2, p1);
|
||||
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStopAudioModeration();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStopVideoModeration();
|
||||
await p1ParticipantsPane.close();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 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.askToUnmute(participant, false);
|
||||
await participant.getNotifications().waitForAskToUnmuteNotification();
|
||||
await unmuteAudioAndCheck(participant, moderator);
|
||||
|
||||
await moderatorParticipantsPane.allowVideo(participant);
|
||||
await participant.getNotifications().waitForAskToUnmuteNotification();
|
||||
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);
|
||||
}
|
||||
146
tests/specs/media/codecSelection.spec.ts
Normal file
146
tests/specs/media/codecSelection.spec.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { VIDEO_CODEC } from '../../../react/features/video-quality/constants';
|
||||
import { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import {
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
const { VP8, VP9, AV1 } = VIDEO_CODEC;
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3' ]
|
||||
});
|
||||
|
||||
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 older versions of Firefox because they don't support VP9 encode.
|
||||
const p1ExpectedCodec = supportsVp9Encode(p1) ? VP9 : VP8;
|
||||
|
||||
await Promise.all([
|
||||
waitForCodec(p1, p1ExpectedCodec),
|
||||
waitForCodec(p2, VP8)
|
||||
]);
|
||||
});
|
||||
|
||||
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 older versions of Firefox because they don't support VP9 encode.
|
||||
const p1ExpectedCodec = supportsVp9Encode(p1) ? VP9 : VP8;
|
||||
const p3ExpectedCodec = (p1.driver.isFirefox && majorVersion < 136) ? VP9 : AV1;
|
||||
|
||||
await Promise.all([
|
||||
waitForCodec(p1, p1ExpectedCodec),
|
||||
waitForCodec(p2, VP8),
|
||||
waitForCodec(p3, p3ExpectedCodec)
|
||||
]);
|
||||
});
|
||||
|
||||
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.
|
||||
await Promise.all([
|
||||
waitForCodec(p1, VP9),
|
||||
waitForCodec(p2, VP9)
|
||||
]);
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
videoQuality: {
|
||||
codecPreferenceOrder: [ 'VP8' ]
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p3 } = ctx;
|
||||
|
||||
await Promise.all([
|
||||
waitForCodec(p1, VP8),
|
||||
waitForCodec(p2, VP8),
|
||||
waitForCodec(p3, VP8)
|
||||
]);
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
// Check of p1 and p2 have switched to VP9.
|
||||
await Promise.all([
|
||||
waitForCodec(p1, VP9),
|
||||
waitForCodec(p2, VP9)
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForCodec(p: Participant, codec: string) {
|
||||
await p.driver.waitUntil(
|
||||
() => p.execute(c => JitsiMeetJS.app.testing.getLocalCameraEncoding() === c, codec),
|
||||
{
|
||||
timeout: 10000,
|
||||
timeoutMsg: `${p.name} failed to use ${codec}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given participant's browser is able to encode VP9. Firefox only supports VP9 encode on version 151 and
|
||||
* above, see https://bugzilla.mozilla.org/show_bug.cgi?id=1633876.
|
||||
*
|
||||
* @param p The participant.
|
||||
*/
|
||||
function supportsVp9Encode(p: Participant) {
|
||||
if (!p.driver.isFirefox) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parseInt(p.driver.capabilities.browserVersion || '0', 10) >= 151;
|
||||
}
|
||||
507
tests/specs/media/desktopSharing.spec.ts
Normal file
507
tests/specs/media/desktopSharing.spec.ts
Normal file
@@ -0,0 +1,507 @@
|
||||
import { SET_LOW_BANDWIDTH_MODE } from '../../../react/features/base/low-bandwidth-mode/actionTypes';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import {
|
||||
checkForScreensharingTile,
|
||||
ensureFourParticipants,
|
||||
ensureOneParticipant,
|
||||
ensureThreeParticipants,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants
|
||||
} from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3', 'p4' ]
|
||||
});
|
||||
|
||||
describe('Desktop sharing', () => {
|
||||
it('start', async () => {
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
backToP2PDelay: 3,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p1.waitForP2PIceConnected();
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// Check if a remote screen share tile is created on p1.
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
|
||||
// Check if a local screen share tile is created on p2.
|
||||
await checkForScreensharingTile(p2, p2);
|
||||
|
||||
expect(await p2.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
});
|
||||
|
||||
it('stop', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
// Check if the local screen share thumbnail disappears on p2.
|
||||
await checkForScreensharingTile(p2, p2, true);
|
||||
|
||||
// Check if the remote screen share thumbnail disappears on p1.
|
||||
await checkForScreensharingTile(p1, p2, true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures screen share is still visible when the call switches from p2p to jvb connection.
|
||||
*/
|
||||
it('p2p to jvb switch', async () => {
|
||||
await ctx.p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
const { p1, p2, p3 } = ctx;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// Check if a remote screen share tile is created on all participants.
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await checkForScreensharingTile(p2, p2);
|
||||
await checkForScreensharingTile(p2, p3);
|
||||
|
||||
await p1.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
await p3.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
await p3.waitForParticipantOnLargeVideo(`${p2EndpointId}-v1`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure screen share is still visible when the call switches from jvb to p2p and back.
|
||||
*/
|
||||
it('p2p to jvb switch and back', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
// Wait for p1 and p2 to switch back to p2p.
|
||||
await p1.waitForP2PIceConnected();
|
||||
await p2.waitForP2PIceConnected();
|
||||
|
||||
// Check if a remote screen share tile is created on p1 and p2 after switching back to p2p.
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await checkForScreensharingTile(p2, p2);
|
||||
|
||||
// The video should be playing.
|
||||
await p1.waitForParticipantOnLargeVideo(`${await p2.getEndpointId()}-v1`);
|
||||
|
||||
// Start desktop share on p1.
|
||||
await p1.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// Check if a new tile for p1's screen share is created on both p1 and p2.
|
||||
await checkForScreensharingTile(p1, p1);
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
backToP2PDelay: 3,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await checkForScreensharingTile(p1, p3);
|
||||
await checkForScreensharingTile(p2, p3);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that screen share is still visible in jvb connection when share is toggled while the users are
|
||||
* in p2p mode, i.e., share is restarted when user is in p2p mode and then the call switches over to jvb mode.
|
||||
*/
|
||||
it('stop screen sharing and back', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
// Stop share on both p1 and p2.
|
||||
await p1.getToolbar().clickStopDesktopSharingButton();
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
await p3.hangup();
|
||||
|
||||
// Wait for p1 and p2 to switch back to p2p.
|
||||
await p1.waitForP2PIceConnected();
|
||||
await p2.waitForP2PIceConnected();
|
||||
|
||||
// Start share on both p1 and p2.
|
||||
await p1.getToolbar().clickDesktopSharingButton();
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// Check if p1 and p2 can see each other's shares in p2p.
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
|
||||
// Add p3 back to the conference and check if p1 and p2's shares are visible on p3.
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
backToP2PDelay: 3,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await checkForScreensharingTile(p1, p3);
|
||||
await checkForScreensharingTile(p2, p3);
|
||||
|
||||
// Add another participant to verify multiple screenshares are visible without gaps in filmstrip.
|
||||
await ensureFourParticipants({
|
||||
configOverwrite: {
|
||||
filmstrip: {
|
||||
stageFilmstripParticipants: 2
|
||||
},
|
||||
startWithAudioMuted: true
|
||||
}
|
||||
});
|
||||
|
||||
const { p4 } = ctx;
|
||||
|
||||
await checkForScreensharingTile(p1, p4);
|
||||
await checkForScreensharingTile(p2, p4);
|
||||
await p3.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
await p4.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensures screen share is visible when a muted screen share track is added to the conference, i.e.,
|
||||
* users starts and stops the share before anyone else joins the call.
|
||||
* The call switches to jvb and then back to p2p.
|
||||
*/
|
||||
it('screen sharing toggle before others join', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureOneParticipant({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
backToP2PDelay: 3,
|
||||
enabled: true
|
||||
},
|
||||
startWithAudioMuted: false
|
||||
}
|
||||
});
|
||||
const { p1 } = ctx;
|
||||
|
||||
// p1 starts share when alone in the call.
|
||||
await p1.getToolbar().clickDesktopSharingButton();
|
||||
await checkForScreensharingTile(p1, p1);
|
||||
|
||||
// p1 stops share.
|
||||
await p1.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
// Call switches to jvb.
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
backToP2PDelay: 3,
|
||||
enabled: true
|
||||
},
|
||||
startWithAudioMuted: false
|
||||
}
|
||||
});
|
||||
const { p2, p3 } = ctx;
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
const p1ScreenShareTileId = `${p1EndpointId}-v1`;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
const p2ScreenShareTileId = `${p2EndpointId}-v1`;
|
||||
|
||||
// p1 starts share again when call switches to jvb.
|
||||
await p1.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// Check p2 and p3 are able to see p1's share.
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
await checkForScreensharingTile(p1, p3);
|
||||
|
||||
await p2.waitForParticipantOnLargeVideo(p1ScreenShareTileId);
|
||||
await p3.waitForParticipantOnLargeVideo(p1ScreenShareTileId);
|
||||
|
||||
// p3 leaves the call.
|
||||
await p3.hangup();
|
||||
|
||||
// Wait for p1 and p2 to switch back to p2p.
|
||||
await p1.waitForP2PIceConnected();
|
||||
await p2.waitForP2PIceConnected();
|
||||
|
||||
// Make sure p2 see's p1's share after the call switches back to p2p.
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
await p2.waitForParticipantOnLargeVideo(p1ScreenShareTileId);
|
||||
|
||||
// p2 starts share when in p2p.
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// Makes sure p2's share is visible on p1.
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForParticipantOnLargeVideo(p2ScreenShareTileId);
|
||||
});
|
||||
|
||||
/**
|
||||
* A case where a non-dominant speaker is sharing screen for a participant in low bandwidth mode
|
||||
* where only a screen share can be received. A bug fixed in jvb 0c5dd91b where the video was not received.
|
||||
*/
|
||||
it('audio only and non dominant screen share', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureOneParticipant();
|
||||
const { p1 } = ctx;
|
||||
|
||||
// a workaround to directly set audio only mode without going through the rest of the settings in the UI
|
||||
await p1.execute(type => {
|
||||
APP?.store?.dispatch({
|
||||
type,
|
||||
lowBandwidthMode: true
|
||||
});
|
||||
APP?.conference?.onToggleLowBandwidthMode();
|
||||
}, SET_LOW_BANDWIDTH_MODE);
|
||||
await p1.getToolbar().clickAudioMuteButton();
|
||||
|
||||
await ensureThreeParticipants({
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
const { p2, p3 } = ctx;
|
||||
|
||||
await p3.getToolbar().clickAudioMuteButton();
|
||||
await p3.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
await checkForScreensharingTile(p3, p1);
|
||||
await checkForScreensharingTile(p3, p2);
|
||||
|
||||
// the video should be playing
|
||||
await p1.driver.waitUntil(() => p1.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'expected remote screen share to be on large'
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A case where first participant is muted (a&v) and enters low bandwidth mode,
|
||||
* the second one is audio muted only and the one sharing (the third) is dominant speaker.
|
||||
* A problem fixed in jitsi-meet 3657c19e and d6ab0a72.
|
||||
*/
|
||||
it('audio only and dominant screen share', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureOneParticipant({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: true
|
||||
}
|
||||
});
|
||||
const { p1 } = ctx;
|
||||
|
||||
// a workaround to directly set audio only mode without going through the rest of the settings in the UI
|
||||
await p1.execute(type => {
|
||||
APP?.store?.dispatch({
|
||||
type,
|
||||
lowBandwidthMode: true
|
||||
});
|
||||
APP?.conference?.onToggleLowBandwidthMode();
|
||||
}, SET_LOW_BANDWIDTH_MODE);
|
||||
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: false
|
||||
},
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: false,
|
||||
startWithVideoMuted: false
|
||||
},
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
const { p2, p3 } = ctx;
|
||||
|
||||
await p3.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
await checkForScreensharingTile(p3, p1);
|
||||
await checkForScreensharingTile(p3, p2);
|
||||
|
||||
// The desktop sharing participant should be on large
|
||||
expect(await p1.getLargeVideo().getResource()).toBe(`${await p3.getEndpointId()}-v1`);
|
||||
|
||||
// the video should be playing
|
||||
await p1.driver.waitUntil(() => p1.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'expected remote screen share to be on large'
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test screensharing with lastN. We add p4 with lastN=2 and verify that it receives the expected streams.
|
||||
*/
|
||||
it('with lastN', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: false,
|
||||
startWithVideoMuted: false
|
||||
},
|
||||
});
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p3.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
await p1.getToolbar().clickAudioMuteButton();
|
||||
await p3.getToolbar().clickAudioMuteButton();
|
||||
|
||||
await ensureFourParticipants({
|
||||
configOverwrite: {
|
||||
channelLastN: 2,
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: false
|
||||
}
|
||||
});
|
||||
const { p4 } = ctx;
|
||||
|
||||
// We now have p1, p2, p3, p4.
|
||||
// p3 is screensharing.
|
||||
// p1, p3, p4 are audio muted, so p2 should eventually become dominant speaker.
|
||||
// Participants should display p3 on-stage because it is screensharing.
|
||||
await checkForScreensharingTile(p3, p1);
|
||||
await checkForScreensharingTile(p3, p2);
|
||||
await checkForScreensharingTile(p3, p4);
|
||||
|
||||
// And the video should be playing
|
||||
expect(await p4.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
|
||||
// Check that there are no gaps in the filmstrip when participant joins during screensharing
|
||||
await p1.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
await p2.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
await p4.getFilmstrip().assertNoGapsInFilmstrip();
|
||||
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// p4 has lastN=2 and has selected p3. With p2 being dominant speaker p4 should eventually
|
||||
// see video for [p3, p2] and p1 as ninja.
|
||||
await p4.waitForNinjaIcon(p1EndpointId);
|
||||
await p4.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Let's switch and check, muting participant 2 and unmuting 1 will leave participant 1 as dominant
|
||||
await p1.getToolbar().clickAudioUnmuteButton();
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// Participant4 should eventually see video for [p3, p1] and p2 as a ninja.
|
||||
await p4.waitForNinjaIcon(p2EndpointId);
|
||||
await p4.waitForRemoteVideo(p1EndpointId);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that when a participant has camera on and starts screenshare, remote participants
|
||||
* see both the camera tile and the screenshare tile simultaneously.
|
||||
* Covers: "Turn video & screenshare on — remotes see both" and
|
||||
* "Camera on before SS starts — receivers still see SS".
|
||||
*/
|
||||
it('camera on when screenshare starts — remotes see both tiles', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
const { p1, p2 } = ctx;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// p2 starts screenshare while camera is already on
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// p1 should see the screenshare tile for p2
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
|
||||
// p1 should still see the camera video for p2
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// p2 should see their own screenshare tile
|
||||
await checkForScreensharingTile(p2, p2);
|
||||
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
// screenshare tile should be gone, camera should still be visible
|
||||
await checkForScreensharingTile(p2, p1, true);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that when a participant joins with video muted, starts screenshare, and then
|
||||
* enables their camera, remote participants see both tiles simultaneously.
|
||||
*/
|
||||
it('join video muted, start screenshare, enable camera — both tiles visible', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
const { p1, p2 } = ctx;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// Mute p2's camera via toolbar (ensures v0 track exists but is muted)
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
await p1.waitForRemoteVideo(p2EndpointId, true);
|
||||
|
||||
// p2 starts screenshare while camera is muted
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
// p1 should see the screenshare tile for p2
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
|
||||
// p2 turns on camera
|
||||
await p2.getToolbar().clickVideoUnmuteButton();
|
||||
|
||||
// p1 should now see both the screenshare tile and the camera video
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verifies that alternating between starting and stopping the camera and screenshare
|
||||
* in various orders does not break either stream for remote participants.
|
||||
*/
|
||||
it('alternating camera and screenshare toggling', async () => {
|
||||
await hangupAllParticipants();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
const { p1, p2 } = ctx;
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// Start SS with camera on — both should be visible
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Stop camera while SS is active — SS should remain visible
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForRemoteVideo(p2EndpointId, true);
|
||||
|
||||
// Re-enable camera while SS is active — both should be visible again
|
||||
await p2.getToolbar().clickVideoUnmuteButton();
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Stop SS while camera is on — camera should remain, SS tile gone
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
await checkForScreensharingTile(p2, p1, true);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Start SS again — both should be visible
|
||||
await p2.getToolbar().clickDesktopSharingButton();
|
||||
await checkForScreensharingTile(p2, p1);
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Stop camera then SS — both should be gone
|
||||
await p2.getToolbar().clickVideoMuteButton();
|
||||
await p2.getToolbar().clickStopDesktopSharingButton();
|
||||
await checkForScreensharingTile(p2, p1, true);
|
||||
await p1.waitForRemoteVideo(p2EndpointId, true);
|
||||
|
||||
// Re-enable camera to verify it still works after all the toggling
|
||||
await p2.getToolbar().clickVideoUnmuteButton();
|
||||
await p1.waitForRemoteVideo(p2EndpointId);
|
||||
});
|
||||
});
|
||||
|
||||
78
tests/specs/media/lastN.spec.ts
Normal file
78
tests/specs/media/lastN.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureFourParticipants, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3', 'p4' ]
|
||||
});
|
||||
|
||||
describe('LastN', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: true,
|
||||
channelLastN: 1
|
||||
},
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
await ensureThreeParticipants({
|
||||
configOverwrite: {
|
||||
channelLastN: 1
|
||||
},
|
||||
skipInMeetingChecks: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('checks', async () => {
|
||||
const { p3 } = ctx;
|
||||
const p3Toolbar = p3.getToolbar();
|
||||
|
||||
await p3.waitForSendMedia();
|
||||
|
||||
await ctx.p1.waitForRemoteVideo(await p3.getEndpointId());
|
||||
|
||||
// Mute audio on participant3.
|
||||
await p3Toolbar.clickAudioMuteButton();
|
||||
|
||||
await ensureFourParticipants({
|
||||
configOverwrite: {
|
||||
channelLastN: 1
|
||||
},
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p1, p2, p4 } = ctx;
|
||||
|
||||
await p4.waitForSendReceiveData();
|
||||
|
||||
// Mute audio on p4 and unmute p3.
|
||||
await p4.getToolbar().clickAudioMuteButton();
|
||||
await p3Toolbar.clickAudioUnmuteButton();
|
||||
|
||||
const p4EndpointId = await p4.getEndpointId();
|
||||
const p3EndpointId = await p3.getEndpointId();
|
||||
|
||||
// Check if p1 starts receiving video from p3 and p4 shows up as ninja.
|
||||
await p1.waitForNinjaIcon(p4EndpointId);
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
|
||||
// At this point, mute video of p3 and others should be receiving p4's video.
|
||||
// Mute p1's video
|
||||
await p3Toolbar.clickVideoMuteButton();
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p3);
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p3);
|
||||
await p1.waitForRemoteVideo(p4EndpointId);
|
||||
|
||||
// Unmute p3's video and others should switch to receiving p3's video.
|
||||
await p3Toolbar.clickVideoUnmuteButton();
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
await p1.waitForNinjaIcon(p4EndpointId);
|
||||
|
||||
// Mute p3's audio and unmute p2's audio. Other endpoints should continue to receive video from p3
|
||||
// even though p2 is the dominant speaker.
|
||||
await p3Toolbar.clickAudioMuteButton();
|
||||
await p2.getToolbar().clickAudioUnmuteButton();
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
});
|
||||
});
|
||||
91
tests/specs/media/lowBandwidthMode.spec.ts
Normal file
91
tests/specs/media/lowBandwidthMode.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Low bandwidth mode', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants());
|
||||
|
||||
/**
|
||||
* Enables low bandwidth mode for p1 and verifies that the other participant sees participant1 as video muted.
|
||||
*/
|
||||
it('set and check', () => setLowBandwidthModeAndCheck(true));
|
||||
|
||||
/**
|
||||
* Verifies that participant1 sees avatars for itself and other participants.
|
||||
*/
|
||||
it('avatars check', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.driver.$('//div[@id="dominantSpeaker"]').waitForDisplayed();
|
||||
|
||||
// Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
|
||||
await p1.assertThumbnailShowsAvatar(p1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Disables low bandwidth mode and verifies that both participants see p1 as not video muted.
|
||||
*/
|
||||
it('disable and check', () => setLowBandwidthModeAndCheck(false));
|
||||
|
||||
/**
|
||||
* Mutes video on participant1, toggles low bandwidth mode twice and then verifies if both participants see
|
||||
* participant1 as video muted.
|
||||
*/
|
||||
it('mute video, set twice and check muted', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
// Mute video on participant1.
|
||||
await p1.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await verifyVideoMute(true);
|
||||
|
||||
// Enable low bandwidth mode.
|
||||
await setLowBandwidthModeAndCheck(true);
|
||||
|
||||
// Disable low bandwidth mode.
|
||||
await p1.getVideoQualityDialog().setVideoQuality(false);
|
||||
|
||||
// p1 should stay muted since it was muted before low bandwidth mode was enabled.
|
||||
await verifyVideoMute(true);
|
||||
});
|
||||
|
||||
it('unmute video and check not muted', async () => {
|
||||
// Unmute video on participant1.
|
||||
await ctx.p1.getToolbar().clickVideoUnmuteButton();
|
||||
|
||||
await verifyVideoMute(false);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles the low bandwidth mode state of a p1 participant and verifies participant sees the low bandwidth mode
|
||||
* label and that p2 participant sees a video mute state for the former.
|
||||
* @param enable
|
||||
*/
|
||||
async function setLowBandwidthModeAndCheck(enable: boolean) {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getVideoQualityDialog().setVideoQuality(enable);
|
||||
|
||||
await verifyVideoMute(enable);
|
||||
|
||||
await p1.driver.$('//div[@id="videoResolutionLabel"][contains(@class, "low-bandwidth-mode")]')
|
||||
.waitForDisplayed({ reverse: !enable });
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that p1 and p2 see p1 as video muted or not.
|
||||
* @param muted
|
||||
*/
|
||||
async function verifyVideoMute(muted: boolean) {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// Verify the observer sees the testee in the desired muted state.
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
|
||||
|
||||
// Verify the testee sees itself in the desired muted state.
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
|
||||
}
|
||||
146
tests/specs/media/mute.spec.ts
Normal file
146
tests/specs/media/mute.spec.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import {
|
||||
checkForScreensharingTile,
|
||||
ensureOneParticipant,
|
||||
ensureTwoParticipants,
|
||||
joinSecondParticipant,
|
||||
} from '../../helpers/participants';
|
||||
import {
|
||||
muteAudioAndCheck,
|
||||
unmuteAudioAndCheck,
|
||||
unmuteVideoAndCheck
|
||||
} from '../helpers/mute';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Mute', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants());
|
||||
|
||||
it('mute p1 and check', () => toggleMuteAndCheck(ctx.p1, ctx.p2, true));
|
||||
|
||||
it('unmute p1 and check', () => toggleMuteAndCheck(ctx.p1, ctx.p2, false));
|
||||
|
||||
it('mute p2 and check', () => toggleMuteAndCheck(ctx.p2, ctx.p1, true));
|
||||
|
||||
it('unmute p2 and check', () => toggleMuteAndCheck(ctx.p2, ctx.p1, false));
|
||||
|
||||
it('p1 mutes p2 and check', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
if (!await p1.isModerator()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await p1.getFilmstrip().muteAudio(p2);
|
||||
|
||||
// and now check whether second participant is muted
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
});
|
||||
|
||||
it('p2 unmute after p1 mute and check', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await unmuteAudioAndCheck(p2, p1);
|
||||
});
|
||||
|
||||
it('p1 mutes before p2 joins', async () => {
|
||||
await ctx.p2.hangup();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getToolbar().clickAudioMuteButton();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p1);
|
||||
|
||||
await toggleMuteAndCheck(p1, p2, false);
|
||||
});
|
||||
|
||||
it('mute before join and screen share after in p2p', () => muteP1BeforeP2JoinsAndScreenshare(true));
|
||||
|
||||
it('mute before join and screen share after with jvb', () => muteP1BeforeP2JoinsAndScreenshare(false));
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 testee The participant whose mute state is to be toggled.
|
||||
* @param observer The participant to verify the mute state of {@code testee}.
|
||||
* @param muted the mute state of {@code testee} expected to be observed by {@code observer}.
|
||||
*/
|
||||
async function toggleMuteAndCheck(
|
||||
testee: Participant,
|
||||
observer: Participant,
|
||||
muted: boolean) {
|
||||
if (muted) {
|
||||
await muteAudioAndCheck(testee, observer);
|
||||
} else {
|
||||
await unmuteAudioAndCheck(testee, observer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Video mutes participant1 before participant2 joins and checks if participant1 can share or unmute video
|
||||
* and that media is being received on participant2 in both the cases.
|
||||
*
|
||||
* @param p2p whether to enable p2p or not.
|
||||
*/
|
||||
async function muteP1BeforeP2JoinsAndScreenshare(p2p: boolean) {
|
||||
await Promise.all([ ctx.p1?.hangup(), ctx.p2?.hangup() ]);
|
||||
|
||||
await ensureOneParticipant({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: p2p
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await joinSecondParticipant({
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: p2p
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { p2 } = ctx;
|
||||
|
||||
if (p2p) {
|
||||
await p2.waitForP2PIceConnected();
|
||||
} else {
|
||||
await p2.waitForIceConnected();
|
||||
}
|
||||
|
||||
await p2.waitForSendMedia();
|
||||
|
||||
// Check if p1 appears video muted on p2.
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
// Start desktop share.
|
||||
await p1.getToolbar().clickDesktopSharingButton();
|
||||
|
||||
await checkForScreensharingTile(p1, p2);
|
||||
|
||||
// we need to pass the id of the fake participant we use for the screensharing
|
||||
await p2.waitForRemoteVideo(`${await p1.getEndpointId()}-v1`);
|
||||
|
||||
// Stop desktop share and unmute video and check for video again.
|
||||
await p1.getToolbar().clickStopDesktopSharingButton();
|
||||
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
await unmuteVideoAndCheck(p1, p2);
|
||||
await p2.waitForRemoteVideo(await p1.getEndpointId());
|
||||
await p2.waitForRemoteVideoDecoding(await p1.getEndpointId());
|
||||
}
|
||||
44
tests/specs/media/pinning.spec.ts
Normal file
44
tests/specs/media/pinning.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Pinning', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants());
|
||||
|
||||
it('p1 click on local', () => ctx.p1.getFilmstrip().pinParticipant(ctx.p1));
|
||||
|
||||
it('p1 click on remote', async () => {
|
||||
await closeToolbarMenu();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().pinParticipant(p2);
|
||||
});
|
||||
|
||||
it('p1 unpin remote', () => ctx.p1.getFilmstrip().unpinParticipant(ctx.p2));
|
||||
|
||||
it('p2 pin remote', () => ctx.p2.getFilmstrip().pinParticipant(ctx.p1));
|
||||
|
||||
it('p2 unpin remote', () => ctx.p2.getFilmstrip().unpinParticipant(ctx.p1));
|
||||
|
||||
it('p2 click on local', () => ctx.p2.getFilmstrip().pinParticipant(ctx.p2));
|
||||
|
||||
it('p2 click on remote', async () => {
|
||||
await closeToolbarMenu();
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p2.getFilmstrip().pinParticipant(p1);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Closes the overflow menu on both participants.
|
||||
*/
|
||||
async function closeToolbarMenu() {
|
||||
await ctx.p1.getToolbar().closeOverflowMenu();
|
||||
await ctx.p2.getToolbar().closeOverflowMenu();
|
||||
}
|
||||
44
tests/specs/media/prejoinDisabled.spec.ts
Normal file
44
tests/specs/media/prejoinDisabled.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants, hangupAllParticipants } from '../../helpers/participants';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Prejoin disabled', () => {
|
||||
it('joining with no prejoin - nested object', async () => {
|
||||
await ensureTwoParticipants({
|
||||
configOverwrite: {
|
||||
prejoinConfig: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
skipInMeetingChecks: true,
|
||||
});
|
||||
|
||||
await checkEveryoneMuted(ctx);
|
||||
|
||||
await hangupAllParticipants();
|
||||
});
|
||||
it('joining with no prejoin - direct url param ', async () => {
|
||||
await ensureTwoParticipants({
|
||||
skipInMeetingChecks: true,
|
||||
urlAppendString: '&config.prejoinConfig.enabled=false'
|
||||
});
|
||||
|
||||
await checkEveryoneMuted(ctx);
|
||||
});
|
||||
});
|
||||
|
||||
async function checkEveryoneMuted({ p1, p2 }: { p1: Participant; p2: Participant; }) {
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p1.getFilmstrip().assertAudioMuteIconIsDisplayed(p1);
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p2.getFilmstrip().assertAudioMuteIconIsDisplayed(p1);
|
||||
|
||||
await p1.getParticipantsPane().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p1.getParticipantsPane().assertAudioMuteIconIsDisplayed(p1);
|
||||
await p2.getParticipantsPane().assertAudioMuteIconIsDisplayed(p2);
|
||||
await p2.getParticipantsPane().assertAudioMuteIconIsDisplayed(p1);
|
||||
}
|
||||
288
tests/specs/media/startMuted.spec.ts
Normal file
288
tests/specs/media/startMuted.spec.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import {
|
||||
checkForScreensharingTile,
|
||||
ensureOneParticipant,
|
||||
ensureTwoParticipants,
|
||||
hangupAllParticipants,
|
||||
joinSecondParticipant,
|
||||
joinThirdParticipant
|
||||
} from '../../helpers/participants';
|
||||
import { unmuteVideoAndCheck } from '../helpers/mute';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2', 'p3' ]
|
||||
});
|
||||
|
||||
describe('Start muted', () => {
|
||||
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);
|
||||
|
||||
// Enable screenshare on p1.
|
||||
await 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);
|
||||
|
||||
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);
|
||||
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p2.waitForIceConnected();
|
||||
await p2.waitForReceiveMedia();
|
||||
|
||||
await joinThirdParticipant(options);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForReceiveMedia();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
const p2ID = await p2.getEndpointId();
|
||||
|
||||
await 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
|
||||
// we need 1 and 2 to be muted so we have a dominant speaker event for correct audio levels calculations
|
||||
await p1.getToolbar().clickAudioMuteButton();
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
await p3.getToolbar().clickAudioUnmuteButton();
|
||||
await 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,
|
||||
startWithVideoMuted: false,
|
||||
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
|
||||
},
|
||||
startWithAudioMuted: false,
|
||||
startWithVideoMuted: false
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
61
tests/specs/media/stopVideo.spec.ts
Normal file
61
tests/specs/media/stopVideo.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { setTestProperties } from '../../helpers/TestProperties';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
import { muteVideoAndCheck, unmuteVideoAndCheck } from '../helpers/mute';
|
||||
|
||||
setTestProperties(__filename, {
|
||||
usesBrowsers: [ 'p1', 'p2' ]
|
||||
});
|
||||
|
||||
describe('Stop video', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants());
|
||||
|
||||
it('stop video and check', () => muteVideoAndCheck(ctx.p1, ctx.p2));
|
||||
|
||||
it('start video and check', () => unmuteVideoAndCheckMedia(ctx.p1, ctx.p2));
|
||||
|
||||
it('start video and check stream', async () => {
|
||||
await muteVideoAndCheck(ctx.p1, ctx.p2);
|
||||
|
||||
// now participant2 should be on large video
|
||||
const largeVideoId = await ctx.p1.getLargeVideo().getId();
|
||||
|
||||
await unmuteVideoAndCheckMedia(ctx.p1, ctx.p2);
|
||||
|
||||
// check if video stream from second participant is still on large video
|
||||
expect(largeVideoId).toBe(await ctx.p1.getLargeVideo().getId());
|
||||
});
|
||||
|
||||
it('stop video on participant and check', () => muteVideoAndCheck(ctx.p2, ctx.p1));
|
||||
|
||||
it('start video on participant and check', () => unmuteVideoAndCheckMedia(ctx.p2, ctx.p1));
|
||||
|
||||
it('stop video on before second joins', async () => {
|
||||
await ctx.p2.hangup();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await p1.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await ensureTwoParticipants();
|
||||
|
||||
const { p2 } = ctx;
|
||||
|
||||
await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1);
|
||||
|
||||
await unmuteVideoAndCheckMedia(p1, p2);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Unmutes the video of testee and checks that observer sees it unmuted and that observer is decoding frames from it.
|
||||
* The mute state alone is signalled over the presence, seeing it does not mean that any media is being received.
|
||||
*
|
||||
* @param testee The participant that unmutes its video.
|
||||
* @param observer The participant that should receive and decode the video of testee.
|
||||
*/
|
||||
async function unmuteVideoAndCheckMedia(testee: Participant, observer: Participant): Promise<void> {
|
||||
await unmuteVideoAndCheck(testee, observer);
|
||||
|
||||
await observer.waitForRemoteVideoDecoding(await testee.getEndpointId());
|
||||
}
|
||||
Reference in New Issue
Block a user