diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx index 7e5a16db04..7e2b61aa0d 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx @@ -245,9 +245,10 @@ class InputStreamLiveSelector extends Component { ]; const deviceList = (listLength > 0) - ? list.map((device) => ( + ? list.map((device, index) => ( { key: `${device.deviceId}-${deviceKind}`, + dataTest: `${deviceKind}-${index + 1}`, label: InputStreamLiveSelector.truncateDeviceName(device.label), customStyles: (device.deviceId === currentDeviceId) && Styled.SelectedLabel, iconRight: (device.deviceId === currentDeviceId) ? 'check' : null, @@ -316,7 +317,7 @@ class InputStreamLiveSelector extends Component { aria-label={intl.formatMessage(intlMessages.leaveAudio)} label={intl.formatMessage(intlMessages.leaveAudio)} accessKey={shortcuts.leaveaudio} - data-test='leaveListenOnly' + data-test="leaveListenOnly" hideLabel color="primary" icon={isListenOnly ? 'listen' : 'volume_level_2'} @@ -397,7 +398,7 @@ class InputStreamLiveSelector extends Component { ? this.renderListenOnlyButton() : this.renderMuteToggleButton()} 0 ? 'hasVolume' : 'hasNoVolume'} min={volumeFloor} low={low} max={high * 1.25} diff --git a/bigbluebutton-tests/playwright/audio/audio.js b/bigbluebutton-tests/playwright/audio/audio.js index 6c45ff6325..89197e5984 100644 --- a/bigbluebutton-tests/playwright/audio/audio.js +++ b/bigbluebutton-tests/playwright/audio/audio.js @@ -1,6 +1,7 @@ const Page = require('../core/page'); const e = require('../core/elements'); -const { ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_TIME } = require('../core/constants'); +const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); +const { connectMicrophone, isAudioItemSelected } = require('./util'); class Audio extends Page { constructor(browser, page) { @@ -18,26 +19,58 @@ class Audio extends Page { } async joinMicrophone() { - const { - autoJoinAudioModal, - skipEchoTest, - skipEchoTestOnJoin, - } = this.settings; - - if (!autoJoinAudioModal) await this.waitAndClick(e.joinAudio); - await this.waitAndClick(e.microphoneButton); - const shouldSkipEchoTest = skipEchoTest || skipEchoTestOnJoin; - if (!shouldSkipEchoTest) { - await this.waitForSelector(e.stopHearingButton); - await this.waitAndClick(e.joinEchoTestButton); - await this.waitForSelector(e.establishingAudioLabel); - await this.wasRemoved(e.establishingAudioLabel, ELEMENT_WAIT_LONGER_TIME); - } - await this.hasElement(e.isTalking); + await connectMicrophone(this); await this.hasElement(e.muteMicButton); await this.waitAndClick(e.audioDropdownMenu); await this.hasElement(e.leaveAudio); } + + async muteYourselfByButton() { + await connectMicrophone(this); + await this.waitAndClick(e.muteMicButton); + await this.wasRemoved(e.isTalking); + await this.hasElement(e.wasTalking); + await this.wasRemoved(e.muteMicButton); + await this.hasElement(e.unmuteMicButton); + await this.wasRemoved(e.talkingIndicator, ELEMENT_WAIT_LONGER_TIME); + } + + async changeAudioInput() { + await connectMicrophone(this); + await this.waitAndClick(e.audioDropdownMenu); + await isAudioItemSelected(this, e.defaultInputAudioDevice); + await this.waitAndClick(e.secondInputAudioDevice); + await this.hasElement(e.isTalking); + await this.hasElement(e.muteMicButton); + await this.waitAndClick(e.audioDropdownMenu); + await isAudioItemSelected(this, e.secondInputAudioDevice); + } + + async keepMuteStateOnRejoin() { + await connectMicrophone(this); + await this.waitAndClick(e.muteMicButton); + await this.hasElement(e.wasTalking); + await this.wasRemoved(e.muteMicButton); + await this.hasElement(e.unmuteMicButton); + await this.waitAndClick(e.audioDropdownMenu); + await this.waitAndClick(e.leaveAudio); + await this.waitAndClick(e.joinAudio); + await this.waitAndClick(e.microphoneButton); + await this.waitAndClick(e.joinEchoTestButton); + await this.waitForSelector(e.establishingAudioLabel); + await this.wasRemoved(e.establishingAudioLabel, ELEMENT_WAIT_LONGER_TIME); + await this.hasElement(e.unmuteMicButton); + } + + async muteYourselfBytalkingIndicator() { + await connectMicrophone(this); + await this.waitAndClick(e.talkingIndicator); + await this.hasElement(e.wasTalking); + await this.wasRemoved(e.muteMicButton); + await this.hasElement(e.unmuteMicButton); + await this.wasRemoved(e.isTalking); + await this.wasRemoved(e.talkingIndicator, ELEMENT_WAIT_LONGER_TIME); + } } exports.Audio = Audio; diff --git a/bigbluebutton-tests/playwright/audio/audio.spec.js b/bigbluebutton-tests/playwright/audio/audio.spec.js index d1b08dfee6..392a811140 100644 --- a/bigbluebutton-tests/playwright/audio/audio.spec.js +++ b/bigbluebutton-tests/playwright/audio/audio.spec.js @@ -1,4 +1,5 @@ const { test } = require('@playwright/test'); +const { MultiUsers } = require('../user/multiusers'); const { Audio } = require('./audio'); test.describe.parallel('Audio', () => { @@ -13,4 +14,37 @@ test.describe.parallel('Audio', () => { await audio.init(true, false); await audio.joinMicrophone(); }); + + test('Mute youself by clicking the mute button @ci', async ({ browser, page }) => { + const audio = new Audio(browser, page); + await audio.init(true, false); + await audio.muteYourselfByButton(); + }); + + test('Change audio input and keep it connected', async ({ browser, page }) => { + const audio = new Audio(browser, page); + await audio.init(true, false); + await audio.changeAudioInput(); + }); + + test('Keep the last mute state after rejoining audio @ci', async ({ browser, page }) => { + const audio = new Audio(browser, page); + await audio.init(true, false); + await audio.keepMuteStateOnRejoin(); + }); + + test.describe.parallel('Talking indicator @ci', () => { + test('Mute youself by clicking the talking indicator', async ({ browser, page }) => { + const audio = new Audio(browser, page); + await audio.init(true, false); + await audio.muteYourselfBytalkingIndicator(); + }); + + test('Mute another user by clicking the talking indicator', async ({ browser, context, page }) => { + const audio = new MultiUsers(browser, context); + await audio.initModPage(page); + await audio.initUserPage(false); + await audio.muteAnotherUser(); + }); + }); }); diff --git a/bigbluebutton-tests/playwright/audio/util.js b/bigbluebutton-tests/playwright/audio/util.js new file mode 100644 index 0000000000..3ced63d5f9 --- /dev/null +++ b/bigbluebutton-tests/playwright/audio/util.js @@ -0,0 +1,33 @@ +const { expect } = require('@playwright/test'); +const e = require('../core/elements'); +const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); + +async function connectMicrophone(testPage) { + const { + autoJoinAudioModal, + skipEchoTest, + skipEchoTestOnJoin, + } = testPage.settings; + + if (!autoJoinAudioModal) await testPage.waitAndClick(e.joinAudio); + await testPage.waitAndClick(e.microphoneButton); + const shouldSkipEchoTest = skipEchoTest || skipEchoTestOnJoin; + if (!shouldSkipEchoTest) { + await testPage.waitForSelector(e.stopHearingButton); + await testPage.waitAndClick(e.joinEchoTestButton); + await testPage.waitForSelector(e.establishingAudioLabel); + await testPage.wasRemoved(e.establishingAudioLabel, ELEMENT_WAIT_LONGER_TIME); + } + await testPage.hasElement(e.isTalking); +} + +async function isAudioItemSelected(testPage, audioSelector) { + await testPage.waitForSelector(audioSelector); + const isSelected = await testPage.page.evaluate(([selector, icon]) => { + return !!document.querySelector(selector).firstChild.querySelector(icon); + }, [audioSelector, e.checkedIcon]); + await expect(isSelected).toBeTruthy(); +} + +exports.connectMicrophone = connectMicrophone; +exports.isAudioItemSelected = isAudioItemSelected; diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index 6a9676060f..68b4327d17 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -33,10 +33,15 @@ exports.establishingAudioLabel = 'span[data-test="establishingAudioLabel"]'; exports.leaveListenOnly = 'button[data-test="leaveListenOnly"]'; exports.leaveAudio = 'li[data-test="leaveAudio"]'; exports.audioDropdownMenu = 'button[data-test="audioDropdownMenu"]'; +exports.defaultInputAudioDevice = 'li[data-test="audioinput-1"]'; +exports.secondInputAudioDevice = 'li[data-test="audioinput-2"]'; exports.microphoneButton = 'button[data-test="microphoneBtn"]'; exports.echoYesButton = 'button[data-test="echoYesBtn"]'; exports.connectingToEchoTest = 'span[data-test="connectingToEchoTest"]'; +exports.hasVolumeEchoTest = 'span[data-test="hasVolume"]'; +exports.hasNoVolumeEchoTest = 'span[data-test="hasNoVolume"]'; exports.isTalking = 'button[data-test="isTalking"]'; +exports.wasTalking = 'button[data-test="wasTalking"]'; exports.talkingIndicator = 'div[data-test="talkingIndicator"]'; exports.unmuteMicButton = 'button[data-test="unmuteMicButton"]'; exports.muteMicButton = 'button[data-test="muteMicButton"]'; @@ -125,6 +130,7 @@ exports.loweringHandToast = 'Your hand has been lowered'; const baseBbbIcon = 'i.icon-bbb-'; exports.unmuteIcon = `${baseBbbIcon}unmute`; exports.listenOnlyIcon = `${baseBbbIcon}listen`; +exports.checkedIcon = `${baseBbbIcon}check`; // Polling exports.pollQuestion = 'Are we good ?'; diff --git a/bigbluebutton-tests/playwright/user/multiusers.js b/bigbluebutton-tests/playwright/user/multiusers.js index 5e69796a18..2cc7925056 100644 --- a/bigbluebutton-tests/playwright/user/multiusers.js +++ b/bigbluebutton-tests/playwright/user/multiusers.js @@ -6,6 +6,7 @@ const { sleep } = require('../core/helpers'); const { checkAvatarIcon, checkIsPresenter } = require('./util'); const { checkTextContent } = require('../core/util'); const { getSettings } = require('../core/settings'); +const { ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); class MultiUsers { constructor(browser, context) { @@ -64,6 +65,17 @@ class MultiUsers { await this.userPage2.init(false, shouldCloseAudioModal, options); } + async muteAnotherUser() { + await this.userPage.joinMicrophone(); + await this.modPage.hasElement(e.joinAudio); + await this.modPage.waitAndClick(e.isTalking); + await this.userPage.hasElement(e.unmuteMicButton); + await this.modPage.hasElement(e.wasTalking); + await this.userPage.hasElement(e.wasTalking); + await this.userPage.wasRemoved(e.talkingIndicator, ELEMENT_WAIT_LONGER_TIME); + await this.modPage.wasRemoved(e.talkingIndicator); + } + async userPresence() { const firstUserOnModPage = this.modPage.getLocator(e.currentUser); const secondUserOnModPage = this.modPage.getLocator(e.userListItem);