Skip to content

Commit

Permalink
feat: Adds audio only tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
damencho committed Dec 9, 2024
1 parent b436f9a commit d9964d4
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 2 deletions.
20 changes: 20 additions & 0 deletions tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { IConfig } from '../../react/features/base/config/configType';
import { urlObjectToString } from '../../react/features/base/util/uri';
import Filmstrip from '../pageobjects/Filmstrip';
import IframeAPI from '../pageobjects/IframeAPI';
import ParticipantsPane from '../pageobjects/ParticipantsPane';
import Toolbar from '../pageobjects/Toolbar';
import VideoQualityDialog from '../pageobjects/VideoQualityDialog';

import { LOG_PREFIX, logInfo } from './browserLogger';
import { IContext } from './types';
Expand Down Expand Up @@ -322,6 +324,24 @@ export class Participant {
return new Filmstrip(this);
}

/**
* Returns the participants pane.
*
* @returns {ParticipantsPane}
*/
getParticipantsPane(): ParticipantsPane {
return new ParticipantsPane(this);
}

/**
* Returns the videoQuality Dialog.
*
* @returns {VideoQualityDialog}
*/
getVideoQualityDialog(): VideoQualityDialog {
return new VideoQualityDialog(this);
}

/**
* Switches to the iframe API context
*/
Expand Down
26 changes: 26 additions & 0 deletions tests/pageobjects/BaseDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Participant } from '../helpers/Participant';

const CLOSE_BUTTON = 'modal-header-close-button';

/**
* Base class for all dialogs.
*/
export default class BaseDialog {
participant: Participant;

/**
* Initializes for a participant.
*
* @param {Participant} participant - The participant.
*/
constructor(participant: Participant) {
this.participant = participant;
}

/**
* Clicks on the X (close) button.
*/
async clickCloseButton(): Promise<void> {
await this.participant.driver.$(`#${CLOSE_BUTTON}`).click();
}
}
35 changes: 33 additions & 2 deletions tests/pageobjects/Filmstrip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class Filmstrip {
* mute icon for the conference participant identified by
* {@code testee}.
*
* @param {Participant} testee - The {@code WebParticipant} for whom we're checking the status of audio muted icon.
* @param {Participant} testee - The {@code Participant} for whom we're checking the status of audio muted icon.
* @param {boolean} reverse - If {@code true}, the method will assert the absence of the "mute" icon;
* otherwise, it will assert its presence.
* @returns {Promise<void>}
Expand All @@ -40,10 +40,41 @@ export default class Filmstrip {
await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Audio mute icon is not displayed for ${testee.name}`
timeoutMsg: `Audio mute icon is ${reverse ? '' : 'not'} displayed for ${testee.name}`
});
}

/**
* Asserts that {@code participant} shows or doesn't show the video mute icon for the conference participant
* identified by {@code testee}.
*
* @param {Participant} testee - The {@code Participant} for whom we're checking the status of audio muted icon.
* @param {boolean} reverse - If {@code true}, the method will assert the absence of the "mute" icon;
* otherwise, it will assert its presence.
* @returns {Promise<void>}
*/
async assertVideoMuteIconIsDisplayed(testee: Participant, reverse = false): Promise<void> {
const isOpen = await this.participant.getParticipantsPane().isOpen();

if (!isOpen) {
await this.participant.getParticipantsPane().open();
}

const id = `participant-item-${await testee.getEndpointId()}`;
const mutedIconXPath
= `//div[@id='${id}']//div[contains(@class, 'indicators')]//*[local-name()='svg' and @id='videoMuted']`;

await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Video mute icon is ${reverse ? '' : 'not'} displayed for ${testee.name}`
});

if (!isOpen) {
await this.participant.getParticipantsPane().close();
}
}

/**
* Returns the remote display name for an endpoint.
* @param endpointId The endpoint id.
Expand Down
47 changes: 47 additions & 0 deletions tests/pageobjects/ParticipantsPane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Participant } from '../helpers/Participant';

/**
* Classname of the closed/hidden participants pane
*/
const PARTICIPANTS_PANE = 'participants_pane';

/**
* Represents the participants pane from the UI.
*/
export default class ParticipantsPane {
private participant: Participant;

/**
* Initializes for a participant.
*
* @param {Participant} participant - The participant.
*/
constructor(participant: Participant) {
this.participant = participant;
}

/**
* Checks if the pane is open.
*/
async isOpen() {
return this.participant.driver.$(`.${PARTICIPANTS_PANE}`).isExisting();
}

/**
* Clicks the "participants" toolbar button to open the participants pane.
*/
async open() {
await this.participant.getToolbar().clickParticipantsPaneButton();

await this.participant.driver.$(`.${PARTICIPANTS_PANE}`).waitForDisplayed();
}

/**
* Clicks the "participants" toolbar button to close the participants pane.
*/
async close() {
await this.participant.getToolbar().clickCloseParticipantsPaneButton();

await this.participant.driver.$(`.${PARTICIPANTS_PANE}`).waitForDisplayed({ reverse: true });
}
}
140 changes: 140 additions & 0 deletions tests/pageobjects/Toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import { Participant } from '../helpers/Participant';

const AUDIO_MUTE = 'Mute microphone';
const AUDIO_UNMUTE = 'Unmute microphone';
const CLOSE_PARTICIPANTS_PANE = 'Close participants pane';
const OVERFLOW_MENU = 'More actions menu';
const OVERFLOW = 'More actions';
const PARTICIPANTS = 'Open participants pane';
const VIDEO_QUALITY = 'Manage video quality';
const VIDEO_MUTE = 'Stop camera';
const VIDEO_UNMUTE = 'Start camera';

/**
* The toolbar elements.
Expand Down Expand Up @@ -63,4 +70,137 @@ export default class Toolbar {
this.participant.log('Clicking on: Audio Unmute Button');
await this.audioUnMuteBtn.click();
}

/**
* The video mute button.
*/
get videoMuteBtn() {
return this.getButton(VIDEO_MUTE);
}

/**
* The video unmute button.
*/
get videoUnMuteBtn() {
return this.getButton(VIDEO_UNMUTE);
}

/**
* Clicks video mute button.
*
* @returns {Promise<void>}
*/
async clickVideoMuteButton(): Promise<void> {
this.participant.log('Clicking on: Video Mute Button');
await this.videoMuteBtn.click();
}

/**
* Clicks video unmute button.
*
* @returns {Promise<void>}
*/
async clickVideoUnmuteButton(): Promise<void> {
this.participant.log('Clicking on: Video Unmute Button');
await this.videoUnMuteBtn.click();
}

/**
* Clicks Participants pane button.
*
* @returns {Promise<void>}
*/
async clickCloseParticipantsPaneButton(): Promise<void> {
this.participant.log('Clicking on: Close Participants pane Button');
await this.getButton(CLOSE_PARTICIPANTS_PANE).click();
}


/**
* Clicks Participants pane button.
*
* @returns {Promise<void>}
*/
async clickParticipantsPaneButton(): Promise<void> {
this.participant.log('Clicking on: Participants pane Button');
await this.getButton(PARTICIPANTS).click();
}

/**
* Clicks on the video quality toolbar button which opens the
* dialog for adjusting max-received video quality.
*/
async clickVideoQualityButton(): Promise<void> {
return this.clickButtonInOverflowMenu(VIDEO_QUALITY);
}

/**
* Ensure the overflow menu is open and clicks on a specified button.
* @param accessibilityLabel The accessibility label of the button to be clicked.
* @private
*/
private async clickButtonInOverflowMenu(accessibilityLabel: string) {
await this.openOverflowMenu();

await this.getButton(accessibilityLabel).click();

await this.closeOverflowMenu();
}

/**
* Checks if the overflow menu is open and visible.
* @private
*/
private async isOverflowMenuOpen() {
return await this.participant.driver.$$(`[aria-label^="${OVERFLOW_MENU}"]`).length > 0;
}

/**
* Clicks on the overflow toolbar button which opens or closes the overflow menu.
* @private
*/
private async clickOverflowButton(): Promise<void> {
await this.getButton(OVERFLOW).click();
}

/**
* Ensure the overflow menu is displayed.
* @private
*/
private async openOverflowMenu() {
if (await this.isOverflowMenuOpen()) {
return;
}

await this.clickOverflowButton();

await this.waitForOverFlowMenu(true);
}

/**
* Ensures the overflow menu is not displayed.
* @private
*/
private async closeOverflowMenu() {
if (!await this.isOverflowMenuOpen()) {
return;
}

await this.clickOverflowButton();

await this.waitForOverFlowMenu(false);
}

/**
* Waits for the overflow menu to be visible or hidden.
* @param visible
* @private
*/
private async waitForOverFlowMenu(visible: boolean) {
await this.participant.driver.$(`[aria-label^="${OVERFLOW_MENU}"]`).waitForDisplayed({
reverse: !visible,
timeout: 3000,
timeoutMsg: `Overflow menu is not ${visible ? 'visible' : 'hidden'}`
});
}
}
42 changes: 42 additions & 0 deletions tests/pageobjects/VideoQualityDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Key } from 'webdriverio';

import BaseDialog from './BaseDialog';

const VIDEO_QUALITY_SLIDER_CLASS = 'custom-slider';

/**
* The video quality dialog.
*/
export default class VideoQualityDialog extends BaseDialog {
/**
* Opens the video quality dialog and sets the video quality to the minimum or maximum definition.
* @param audioOnly - Whether to set the video quality to audio only (minimum).
* @private
*/
async setVideoQuality(audioOnly: boolean) {
await this.participant.getToolbar().clickVideoQualityButton();

const videoQualitySlider = this.participant.driver.$(`.${VIDEO_QUALITY_SLIDER_CLASS}`);

const audioOnlySliderValue = parseInt(await videoQualitySlider.getAttribute('min'), 10);

const maxDefinitionSliderValue = parseInt(await videoQualitySlider.getAttribute('max'), 10);
const activeValue = parseInt(await videoQualitySlider.getAttribute('value'), 10);

const targetValue = audioOnly ? audioOnlySliderValue : maxDefinitionSliderValue;
const distanceToTargetValue = targetValue - activeValue;
const keyDirection = distanceToTargetValue > 0 ? Key.ArrowRight : Key.ArrowLeft;

// we need to click the element to activate it so it will receive the keys
await videoQualitySlider.click();

// Move the slider to the target value.
for (let i = 0; i < Math.abs(distanceToTargetValue); i++) {

await this.participant.driver.keys(keyDirection);
}

// Close the video quality dialog.
await this.clickCloseButton();
}
}
Loading

0 comments on commit d9964d4

Please sign in to comment.