Skip to content

Commit

Permalink
feat(tests): Adds breakout tests. (#15414)
Browse files Browse the repository at this point in the history
* feat(tests): Introduces BasePageObject.

* fix(tests): Use wdio aria selector where possible.

* fix(tests): Correct test exclusion for Firefox.

* fix(tests): Rearrange code.

* feat(tests): Adds breakout tests.
  • Loading branch information
damencho authored Dec 20, 2024
1 parent c23684e commit c6cce92
Show file tree
Hide file tree
Showing 17 changed files with 870 additions and 147 deletions.
26 changes: 26 additions & 0 deletions tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { multiremotebrowser } from '@wdio/globals';

import { IConfig } from '../../react/features/base/config/configType';
import { urlObjectToString } from '../../react/features/base/util/uri';
import BreakoutRooms from '../pageobjects/BreakoutRooms';
import Filmstrip from '../pageobjects/Filmstrip';
import IframeAPI from '../pageobjects/IframeAPI';
import Notifications from '../pageobjects/Notifications';
Expand Down Expand Up @@ -251,6 +252,22 @@ export class Participant {
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
}

/**
* Checks if the meeting supports breakout rooms.
*/
async isBreakoutRoomsSupported() {
return await this.driver.execute(() => typeof APP !== 'undefined'
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
}

/**
* Checks if the participant is in breakout room.
*/
async isInBreakoutRoom() {
return await this.driver.execute(() => typeof APP !== 'undefined'
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isBreakoutRoom());
}

/**
* Waits to join the muc.
*
Expand Down Expand Up @@ -321,6 +338,15 @@ export class Participant {
});
}

/**
* Returns the BreakoutRooms for this participant.
*
* @returns {BreakoutRooms}
*/
getBreakoutRooms(): BreakoutRooms {
return new BreakoutRooms(this);
}

/**
* Returns the toolbar for this participant.
*
Expand Down
18 changes: 18 additions & 0 deletions tests/helpers/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { v4 as uuidv4 } from 'uuid';
import { Participant } from './Participant';
import { IContext, IJoinOptions } from './types';

const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';

/**
* Ensure that there is on participant.
*
Expand Down Expand Up @@ -237,3 +239,19 @@ export function parseJid(str: string): {
resource: domainParts.length > 0 ? domainParts[1] : undefined
};
}

/**
* Check the subject of the participant.
* @param participant
* @param subject
*/
export async function checkSubject(participant: Participant, subject: string) {
const localTile = participant.driver.$(SUBJECT_XPATH);

await localTile.waitForExist();
await localTile.moveTo();

const txt = await localTile.getText();

expect(txt.startsWith(subject)).toBe(true);
}
14 changes: 2 additions & 12 deletions tests/pageobjects/AVModerationMenu.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Participant } from '../helpers/Participant';
import BasePageObject from './BasePageObject';

const START_AUDIO_MODERATION = 'participants-pane-context-menu-start-audio-moderation';
const STOP_AUDIO_MODERATION = 'participants-pane-context-menu-stop-audio-moderation';
Expand All @@ -8,17 +8,7 @@ const STOP_VIDEO_MODERATION = 'participants-pane-context-menu-stop-video-moderat
/**
* Represents the Audio Video Moderation menu in the participants pane.
*/
export default class AVModerationMenu {
private participant: Participant;

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

export default class AVModerationMenu extends BasePageObject {
/**
* Clicks the start audio moderation menu item.
*/
Expand Down
15 changes: 2 additions & 13 deletions tests/pageobjects/BaseDialog.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import { Participant } from '../helpers/Participant';
import BasePageObject from './BasePageObject';

const CLOSE_BUTTON = 'modal-header-close-button';
const OK_BUTTON = 'modal-dialog-ok-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;
}

export default class BaseDialog extends BasePageObject {
/**
* Clicks on the X (close) button.
*/
Expand Down
16 changes: 16 additions & 0 deletions tests/pageobjects/BasePageObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Participant } from '../helpers/Participant';

/**
* Represents the base page object.
* All page object has the current participant (holding the driver/browser session).
*/
export default class BasePageObject {
participant: Participant;

/**
* Represents the base page object.
*/
constructor(participant: Participant) {
this.participant = participant;
}
}
230 changes: 230 additions & 0 deletions tests/pageobjects/BreakoutRooms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { Participant } from '../helpers/Participant';

import BaseDialog from './BaseDialog';
import BasePageObject from './BasePageObject';

const BREAKOUT_ROOMS_CLASS = 'breakout-room-container';
const ADD_BREAKOUT_ROOM = 'Add breakout room';
const MORE_LABEL = 'More';
const LEAVE_ROOM_LABEL = 'Leave breakout room';
const AUTO_ASSIGN_LABEL = 'Auto assign to breakout rooms';

/**
* Represents a single breakout room and the operations for it.
*/
class BreakoutRoom extends BasePageObject {
title: string;
id: string;
count: number;

/**
* Constructs a breakout room.
*/
constructor(participant: Participant, title: string, id: string) {
super(participant);

this.title = title;
this.id = id;

const tMatch = title.match(/.*\((.*)\)/);

if (tMatch) {
this.count = parseInt(tMatch[1], 10);
}
}

/**
* Returns room name.
*/
get name() {
return this.title.split('(')[0].trim();
}

/**
* Returns the number of participants in the room.
*/
get participantCount() {
return this.count;
}

/**
* Collapses the breakout room.
*/
async collapse() {
const collapseElem = this.participant.driver.$(
`div[data-testid="${this.id}"]`);

await collapseElem.click();
}

/**
* Joins the breakout room.
*/
async joinRoom() {
const joinButton = this.participant.driver
.$(`button[data-testid="join-room-${this.id}"]`);

await joinButton.waitForClickable();
await joinButton.click();
}

/**
* Removes the breakout room.
*/
async removeRoom() {
await this.openContextMenu();

const removeButton = this.participant.driver.$(`#remove-room-${this.id}`);

await removeButton.waitForClickable();
await removeButton.click();
}

/**
* Renames the breakout room.
*/
async renameRoom(newName: string) {
await this.openContextMenu();

const renameButton = this.participant.driver.$(`#rename-room-${this.id}`);

await renameButton.click();

const newNameInput = this.participant.driver.$('input[name="breakoutRoomName"]');

await newNameInput.waitForStable();
await newNameInput.setValue(newName);

await new BaseDialog(this.participant).clickOkButton();
}

/**
* Closes the breakout room.
*/
async closeRoom() {
await this.openContextMenu();

const closeButton = this.participant.driver.$(`#close-room-${this.id}`);

await closeButton.waitForClickable();
await closeButton.click();
}

/**
* Opens the context menu.
* @private
*/
private async openContextMenu() {
const listItem = this.participant.driver.$(`div[data-testid="${this.id}"]`);

await listItem.click();

const button = listItem.$(`aria/${MORE_LABEL}`);

await button.waitForClickable();
await button.click();
}
}

/**
* All breakout rooms objects and operations.
*/
export default class BreakoutRooms extends BasePageObject {
/**
* Returns the number of breakout rooms.
*/
async getRoomsCount() {
const participantsPane = this.participant.getParticipantsPane();

if (!await participantsPane.isOpen()) {
await participantsPane.open();
}

return await this.participant.driver.$$(`.${BREAKOUT_ROOMS_CLASS}`).length;
}

/**
* Adds a breakout room.
*/
async addBreakoutRoom() {
const participantsPane = this.participant.getParticipantsPane();

if (!await participantsPane.isOpen()) {
await participantsPane.open();
}

const addBreakoutButton = this.participant.driver.$(`aria/${ADD_BREAKOUT_ROOM}`);

await addBreakoutButton.waitForDisplayed();
await addBreakoutButton.click();
}

/**
* Returns all breakout rooms.
*/
async getRooms(): Promise<BreakoutRoom[]> {
const rooms = this.participant.driver.$$(`.${BREAKOUT_ROOMS_CLASS}`);

return rooms.map(async room => new BreakoutRoom(
this.participant, await room.$('span').getText(), await room.getAttribute('data-testid')));
}

/**
* Leave by clicking the leave button in participant pane.
*/
async leaveBreakoutRoom() {
const participantsPane = this.participant.getParticipantsPane();

if (!await participantsPane.isOpen()) {
await participantsPane.open();
}

const leaveButton = this.participant.driver.$(`aria/${LEAVE_ROOM_LABEL}`);

await leaveButton.isClickable();
await leaveButton.click();
}

/**
* Auto assign participants to breakout rooms.
*/
async autoAssignToBreakoutRooms() {
const button = this.participant.driver.$(`aria/${AUTO_ASSIGN_LABEL}`);

await button.waitForClickable();
await button.click();
}

/**
* Tries to send a participant to a breakout room.
*/
async sendParticipantToBreakoutRoom(participant: Participant, roomName: string) {
const participantsPane = this.participant.getParticipantsPane();

await participantsPane.selectParticipant(participant);
await participantsPane.openParticipantContextMenu(participant);

const sendButton = this.participant.driver.$(`aria/${roomName}`);

await sendButton.waitForClickable();
await sendButton.click();
}

// /**
// * Open context menu for given participant.
// */
// async openParticipantContextMenu(participant: Participant) {
// const listItem = this.participant.driver.$(
// `div[@id="participant-item-${await participant.getEndpointId()}"]`);
//
// await listItem.waitForDisplayed();
// await listItem.moveTo();
//
// const button = listItem.$(`aria/${PARTICIPANT_MORE_LABEL}`);
//
// await button.waitForClickable();
// await button.click();
// }
}


14 changes: 2 additions & 12 deletions tests/pageobjects/Filmstrip.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import { Participant } from '../helpers/Participant';

import BaseDialog from './BaseDialog';
import BasePageObject from './BasePageObject';

/**
* Filmstrip elements.
*/
export default class Filmstrip {
private participant: Participant;

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

export default class Filmstrip extends BasePageObject {
/**
* Asserts that {@code participant} shows or doesn't show the audio
* mute icon for the conference participant identified by
Expand Down
Loading

0 comments on commit c6cce92

Please sign in to comment.