Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tests): Adds follow-me and invite dialog test. #15476

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tests/env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@
#WEBHOOKS_PROXY_URL=
# A shared secret to authenticate the webhook proxy connection
#WEBHOOKS_PROXY_SHARED_SECRET=

# A rest URL to be used by dial-in tests to invite jigasi to the conference
#DIAL_IN_REST_URL=
31 changes: 25 additions & 6 deletions tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,22 @@ export class Participant {
async () => await this.driver.execute(() => document.readyState === 'complete'),
{
timeout: 30_000, // 30 seconds
timeoutMsg: 'Timeout waiting for Page Load Request to complete.'
timeoutMsg: `Timeout waiting for Page Load Request to complete for ${this.name}.`
}
);
}

/**
* Waits for the tile view to display.
*/
async waitForTileViewDisplay(reverse = false) {
await this.driver.$('//div[@id="videoconference_page" and contains(@class, "tile-view")]').waitForDisplayed({
reverse,
timeout: 10_000,
timeoutMsg: `Tile view did not display in 10s for ${this.name}`
});
}

/**
* Checks if the participant is in the meeting.
*/
Expand Down Expand Up @@ -284,7 +295,7 @@ export class Participant {
() => this.isInMuc(),
{
timeout: 10_000, // 10 seconds
timeoutMsg: 'Timeout waiting to join muc.'
timeoutMsg: `Timeout waiting to join muc for ${this.name}`
}
);
}
Expand All @@ -300,7 +311,7 @@ export class Participant {
return driver.waitUntil(async () =>
await driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
timeout: 15_000,
timeoutMsg: 'expected ICE to be connected for 15s'
timeoutMsg: `expected ICE to be connected for 15s for ${this.name}`
});
}

Expand All @@ -309,7 +320,8 @@ export class Participant {
*
* @returns {Promise<void>}
*/
async waitForSendReceiveData(timeout = 15_000, msg = 'expected to receive/send data in 15s'): Promise<void> {
async waitForSendReceiveData(
timeout = 15_000, msg = `expected to receive/send data in 15s for ${this.name}`): Promise<void> {
const driver = this.driver;

return driver.waitUntil(async () =>
Expand Down Expand Up @@ -340,7 +352,7 @@ export class Participant {
return driver.waitUntil(async () =>
await driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
timeout: 15_000,
timeoutMsg: 'expected remote streams in 15s'
timeoutMsg: `expected remote streams in 15s for ${this.name}`
});
}

Expand All @@ -357,7 +369,7 @@ export class Participant {
return driver.waitUntil(async () =>
await driver.execute(count => APP.conference.listMembers().length === count, number), {
timeout: 15_000,
timeoutMsg: msg || `not the expected participants ${number} in 15s`
timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
});
}

Expand Down Expand Up @@ -531,6 +543,13 @@ export class Participant {
return await this.driver.execute(() => APP.UI.getLargeVideoID());
}

/**
* Returns the source of the large video currently shown.
*/
async getLargeVideoId() {
return await this.driver.execute('return document.getElementById("largeVideo").srcObject.id');
}

/**
* Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
* There are 3 options for avatar:
Expand Down
48 changes: 48 additions & 0 deletions tests/pageobjects/Filmstrip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ export default class Filmstrip extends BasePageObject {
return await remoteDisplayName.getText();
}

/**
* Returns the remote video id of a participant with endpointID.
* @param endpointId
*/
async getRemoteVideoId(endpointId: string) {
const remoteDisplayName = this.participant.driver.$(`span[id="participant_${endpointId}"]`);

await remoteDisplayName.moveTo();

return await this.participant.driver.execute(eId =>
document.evaluate(`//span[@id="participant_${eId}"]//video`,
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue?.srcObject?.id, endpointId);
}

/**
* Returns the local video id.
*/
async getLocalVideoId() {
return await this.participant.driver.execute(
'return document.getElementById("localVideo_container").srcObject.id');
}

/**
* Pins a participant by clicking on their thumbnail.
* @param participant The participant.
Expand Down Expand Up @@ -162,4 +184,30 @@ export default class Filmstrip extends BasePageObject {
timeoutMsg: `Local video thumbnail is${hidden ? '' : ' not'} displayed for ${this.participant.name}`
});
}

/**
* Toggles the filmstrip.
*/
async toggle() {
const toggleButton = this.participant.driver.$('#toggleFilmstripButton');

await toggleButton.moveTo();
await toggleButton.waitForDisplayed();
await toggleButton.click();
}

/**
* Asserts that the remote videos are hidden or not.
* @param reverse
*/
async assertRemoteVideosHidden(reverse = false) {
await this.participant.driver.waitUntil(
async () =>
await this.participant.driver.$$('//div[@id="remoteVideos" and contains(@class, "hidden")]').length > 0,
{
timeout: 10_000, // 10 seconds
timeoutMsg: `Timeout waiting fore remote videos to be hidden: ${!reverse}.`
}
);
}
}
62 changes: 60 additions & 2 deletions tests/pageobjects/InviteDialog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import BaseDialog from './BaseDialog';

const CONFERENCE_ID = 'conference-id';
const CONFERENCE_URL = 'invite-more-dialog-conference-url';
const DIALOG_CONTAINER = 'invite-more-dialog';
const MORE_NUMBERS = 'more-numbers';
const PHONE_NUMBER = 'phone-number';

/**
* Represents the invite dialog in a particular participant.
Expand Down Expand Up @@ -31,14 +34,69 @@ export default class InviteDialog extends BaseDialog {
async getPinNumber() {
await this.open();

const elem = this.participant.driver.$(`.${CONFERENCE_ID}`);
return (await this.getValueAfterColon(CONFERENCE_ID)).replace(/[# ]/g, '');
}

/**
* Private helper to get values after colons. The invite dialog lists conference specific information
* after a label, followed by a colon.
*
* @param className
* @private
*/
private async getValueAfterColon(className: string) {
const elem = this.participant.driver.$(`.${className}`);

await elem.waitForExist({ timeout: 5000 });

const fullText = await elem.getText();

this.participant.log(`Extracted text in invite dialog: ${fullText}`);

return fullText.split(':')[1].trim().replace(/[# ]/g, '');
return fullText.split(':')[1].trim();
}

/**
* Returns the meeting url displayed in the dialog.
*/
async getMeetingURL() {
const elem = this.participant.driver.$(`.${CONFERENCE_URL}`);

await elem.waitForExist();

return (await elem.getText())?.trim();
}

/**
* Waits for the dialog to be open or closed.
* @param reverse
*/
async waitTillOpen(reverse = false) {
await this.participant.driver.waitUntil(
/* eslint-disable no-extra-parens */
async () => (reverse ? !await this.isOpen() : await this.isOpen()),
{
timeout: 2_000,
timeoutMsg: `invite dialog did not ${reverse ? 'close' : 'open'}`
}
);
}

/**
* Gets the string that contains the dial in number for the current conference.
*/
async getDialInNumber() {
return await this.getValueAfterColon(PHONE_NUMBER);
}

/**
* Clicks the link to open a page to show all available dial in numbers.
*/
async openDialInNumbersPage() {
const moreNumbers = this.participant.driver.$(`.${MORE_NUMBERS}`);

await moreNumbers.waitForExist();
await moreNumbers.waitForClickable();
await moreNumbers.click();
}
}
52 changes: 48 additions & 4 deletions tests/pageobjects/SettingsDialog.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import BaseDialog from './BaseDialog';

const EMAIL_FIELD = '#setEmail';
const FOLLOW_ME_CHECKBOX = '//input[@name="follow-me"]';
const HIDE_SELF_VIEW_CHECKBOX = '//input[@name="hide-self-view"]';
const SETTINGS_DIALOG_CONTENT = '.settings-pane';
const X_PATH_MODERATOR_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="Moderator"]';
const X_PATH_MORE_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="General"]';
const X_PATH_PROFILE_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="Profile"]';

Expand Down Expand Up @@ -37,12 +39,19 @@ export default class SettingsDialog extends BaseDialog {
}

/**
* Selects the Profile tab to be displayed.
* Selects the More tab to be displayed.
*/
async openMoreTab() {
await this.openTab(X_PATH_MORE_TAB);
}

/**
* Selects the moderator tab to be displayed.
*/
async openModeratorTab() {
await this.openTab(X_PATH_MODERATOR_TAB);
}

/**
* Enters the passed in email into the email field.
* @param email
Expand Down Expand Up @@ -75,14 +84,49 @@ export default class SettingsDialog extends BaseDialog {
async setHideSelfView(hideSelfView: boolean) {
await this.openMoreTab();

const checkbox = this.participant.driver.$(HIDE_SELF_VIEW_CHECKBOX);
await this.setCheckbox(HIDE_SELF_VIEW_CHECKBOX, hideSelfView);
}

/**
* Sets the follow me feature to enabled/disabled.
* @param enable
*/
async setFollowMe(enable: boolean) {
await this.openModeratorTab();

await this.setCheckbox(FOLLOW_ME_CHECKBOX, enable);
}

/**
* Returns true if the follow me checkbox is displayed in the settings dialog.
*/
async isFollowMeDisplayed() {
const elem = this.participant.driver.$(X_PATH_MODERATOR_TAB);

if (!await elem.isExisting()) {
return false;
}

await this.openModeratorTab();

return await this.participant.driver.$$(FOLLOW_ME_CHECKBOX).length > 0;
}

/**
* Sets the state of a checkbox.
* @param selector
* @param enable
* @private
*/
private async setCheckbox(selector: string, enable: boolean) {
const checkbox = this.participant.driver.$(selector);

await checkbox.waitForExist();

if (hideSelfView !== await checkbox.isSelected()) {
if (enable !== await checkbox.isSelected()) {
// we show a div with svg and text after the input and those elements grab the click
// so we need to click on the parent element
await this.participant.driver.$(`${HIDE_SELF_VIEW_CHECKBOX}//ancestor::div[1]`).click();
await this.participant.driver.$(`${selector}//ancestor::div[1]`).click();
}
}
}
1 change: 0 additions & 1 deletion tests/pageobjects/Toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ export default class Toolbar extends BasePageObject {
await this.getButton(CLOSE_PARTICIPANTS_PANE).click();
}


/**
* Clicks Participants pane button.
*
Expand Down
5 changes: 2 additions & 3 deletions tests/specs/2way/fakeDialInAudio.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import process from 'node:process';

import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants';
import { cleanup, waitForAudioFromDialInParticipant } from '../helpers/DialIn';
import { cleanup, isDialInEnabled, waitForAudioFromDialInParticipant } from '../helpers/DialIn';

describe('Fake Dial-In - ', () => {
it('join participant', async () => {
Expand All @@ -17,8 +17,7 @@ describe('Fake Dial-In - ', () => {
await ensureOneParticipant(ctx);

// check dial-in is enabled, so skip
if (await ctx.p1.driver.execute(() => Boolean(
config.dialInConfCodeUrl && config.dialInNumbersUrl && config.hosts?.muc))) {
if (await isDialInEnabled(ctx.p1)) {
ctx.skipSuiteTests = true;
}
});
Expand Down
Loading
Loading