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): AVModeration tests. #15408

Merged
merged 8 commits into from
Dec 19, 2024
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
1,795 changes: 1,120 additions & 675 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/jasmine": "5.1.4",
"@types/js-md5": "0.4.3",
"@types/jsonwebtoken": "9.0.7",
"@types/lodash-es": "4.17.12",
"@types/mocha": "10.0.10",
saghul marked this conversation as resolved.
Show resolved Hide resolved
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",
Expand All @@ -162,12 +162,12 @@
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"@wdio/allure-reporter": "9.2.14",
"@wdio/cli": "9.4.1",
"@wdio/globals": "9.4.1",
"@wdio/jasmine-framework": "9.4.1",
"@wdio/junit-reporter": "9.2.14",
"@wdio/local-runner": "9.4.1",
"@wdio/allure-reporter": "9.4.3",
"@wdio/cli": "9.4.3",
"@wdio/globals": "9.4.3",
"@wdio/junit-reporter": "9.4.3",
"@wdio/local-runner": "9.4.3",
"@wdio/mocha-framework": "9.4.3",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
Expand All @@ -191,7 +191,7 @@
"ts-loader": "9.4.2",
"typescript": "5.0.4",
"unorm": "1.6.0",
"webdriverio": "9.4.1",
"webdriverio": "9.4.3",
"webpack": "5.95.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",
Expand Down
1 change: 1 addition & 0 deletions tests/env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# The base url that will be used for the test (default will be using "https://alpha.jitsi.net")
# If there is a tenant in the URL it must end with a slash (e.g. "https://alpha.jitsi.net/sometenant/")
#BASE_URL=

# To be able to match a domain to a specific address
Expand Down
2 changes: 1 addition & 1 deletion tests/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IContext } from './helpers/types';

declare global {
const context: IContext;
const ctx: IContext;
}
41 changes: 26 additions & 15 deletions tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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 Notifications from '../pageobjects/Notifications';
import ParticipantsPane from '../pageobjects/ParticipantsPane';
import SettingsDialog from '../pageobjects/SettingsDialog';
import Toolbar from '../pageobjects/Toolbar';
Expand Down Expand Up @@ -112,13 +113,13 @@ export class Participant {
/**
* Joins conference.
*
* @param {IContext} context - The context.
* @param {IContext} ctx - The context.
* @param {IJoinOptions} options - Options for joining.
* @returns {Promise<void>}
*/
async joinConference(context: IContext, options: IJoinOptions = {}): Promise<void> {
async joinConference(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
const config = {
room: context.roomName,
room: ctx.roomName,
configOverwrite: this.config,
interfaceConfigOverwrite: {
SHOW_CHROME_EXTENSION_BANNER: false
Expand All @@ -132,17 +133,17 @@ export class Participant {
};
}

if (context.iframeAPI) {
if (ctx.iframeAPI) {
config.room = 'iframeAPITest.html';
}

let url = urlObjectToString(config) || '';

if (context.iframeAPI) {
if (ctx.iframeAPI) {
const baseUrl = new URL(this.driver.options.baseUrl || '');

// @ts-ignore
url = `${this.driver.iframePageBase}${url}&domain="${baseUrl.host}"&room="${context.roomName}"`;
url = `${this.driver.iframePageBase}${url}&domain="${baseUrl.host}"&room="${ctx.roomName}"`;

if (baseUrl.pathname.length > 1) {
// remove leading slash
Expand All @@ -155,17 +156,12 @@ export class Participant {

await this.driver.setTimeout({ 'pageLoad': 30000 });

// workaround for https://github.com/webdriverio/webdriverio/issues/13956
if (url.startsWith('file://')) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
await this.driver.url(url).catch(() => {});
} else {
await this.driver.url(url.substring(1)); // drop the leading '/' so we can use the tenant if any
}
// drop the leading '/' so we can use the tenant if any
await this.driver.url(url.startsWith('/') ? url.substring(1) : url);

await this.waitForPageToLoad();

if (context.iframeAPI) {
if (ctx.iframeAPI) {
const mainFrame = this.driver.$('iframe');

await this.driver.switchFrame(mainFrame);
Expand Down Expand Up @@ -247,6 +243,14 @@ export class Participant {
return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
}

/**
* Checks if the participant is a moderator in the meeting.
*/
async isModerator() {
return await this.driver.execute(() => typeof APP !== 'undefined'
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
}

/**
* Waits to join the muc.
*
Expand Down Expand Up @@ -335,6 +339,13 @@ export class Participant {
return new Filmstrip(this);
}

/**
* Returns the notifications.
*/
getNotifications(): Notifications {
return new Notifications(this);
}

/**
* Returns the participants pane.
*
Expand Down Expand Up @@ -489,7 +500,7 @@ export class Participant {
async assertDisplayNameVisibleOnStage(value: string) {
const displayNameEl = this.driver.$('div[data-testid="stage-display-name"]');

expect(await displayNameEl.isDisplayed()).toBeTrue();
expect(await displayNameEl.isDisplayed()).toBe(true);
expect(await displayNameEl.getText()).toBe(value);
}
}
113 changes: 51 additions & 62 deletions tests/helpers/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,19 @@ import process from 'node:process';
import { v4 as uuidv4 } from 'uuid';

import { Participant } from './Participant';
import WebhookProxy from './WebhookProxy';
import { IContext, IJoinOptions } from './types';

/**
* Generate a random room name.
* Everytime we generate a name and iframeAPI is enabled and there is a configured
* webhooks proxy we connect to it with the new room name.
*
* @returns {string} - The random room name.
*/
function generateRandomRoomName(): string {
const roomName = `jitsimeettorture-${crypto.randomUUID()}`;

if (context.iframeAPI && !context.webhooksProxy
&& process.env.WEBHOOKS_PROXY_URL && process.env.WEBHOOKS_PROXY_SHARED_SECRET) {
context.webhooksProxy = new WebhookProxy(`${process.env.WEBHOOKS_PROXY_URL}&room=${roomName}`,
process.env.WEBHOOKS_PROXY_SHARED_SECRET);
context.webhooksProxy.connect();
}

return roomName;
}

/**
* Ensure that there is on participant.
*
* @param {IContext} context - The context.
* @param {IContext} ctx - The context.
* @param {IJoinOptions} options - The options to use when joining the participant.
* @returns {Promise<void>}
*/
export async function ensureOneParticipant(context: IContext, options?: IJoinOptions): Promise<void> {
if (!context.roomName) {
context.roomName = generateRandomRoomName();
}
export async function ensureOneParticipant(ctx: IContext, options?: IJoinOptions): Promise<void> {
ctx.p1 = new Participant('participant1');

context.p1 = new Participant('participant1');

await context.p1.joinConference(context, {
await ctx.p1.joinConference(ctx, {
...options,
skipInMeetingChecks: true
});
Expand All @@ -50,70 +25,72 @@ export async function ensureOneParticipant(context: IContext, options?: IJoinOpt
/**
* Ensure that there are three participants.
*
* @param {Object} context - The context.
* @param {Object} ctx - The context.
* @returns {Promise<void>}
*/
export async function ensureThreeParticipants(context: IContext): Promise<void> {
if (!context.roomName) {
context.roomName = generateRandomRoomName();
}
export async function ensureThreeParticipants(ctx: IContext): Promise<void> {
await joinTheModeratorAsP1(ctx);

const p1 = new Participant('participant1');
const p2 = new Participant('participant2');
const p3 = new Participant('participant3');

context.p1 = p1;
context.p2 = p2;
context.p3 = p3;
ctx.p2 = p2;
ctx.p3 = p3;

// these need to be all, so we get the error when one fails
await Promise.all([
p1.joinConference(context),
p2.joinConference(context),
p3.joinConference(context)
p2.joinConference(ctx),
p3.joinConference(ctx)
]);

await Promise.all([
p1.waitForRemoteStreams(2),
p2.waitForRemoteStreams(2),
p3.waitForRemoteStreams(2)
]);
}

/**
* Ensure that there are two participants.
* Ensure that the first participant is moderator.
*
* @param {Object} context - The context.
* @param {Object} ctx - The context.
* @param {IJoinOptions} options - The options to join.
* @returns {Promise<void>}
*/
export async function ensureTwoParticipants(context: IContext, options?: IJoinOptions): Promise<void> {
if (!context.roomName) {
context.roomName = generateRandomRoomName();
}

async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
const p1DisplayName = 'participant1';
let token;

// if it is jaas create the first one to be moderator and second not moderator
if (context.jwtPrivateKeyPath) {
if (ctx.jwtPrivateKeyPath && !options?.skipFirstModerator) {
token = getModeratorToken(p1DisplayName);
}

// make sure the first participant is moderator, if supported by deployment
await _joinParticipant(p1DisplayName, context.p1, p => {
context.p1 = p;
await _joinParticipant(p1DisplayName, ctx.p1, p => {
ctx.p1 = p;
}, {
...options,
skipInMeetingChecks: true
}, token);
}

/**
* Ensure that there are two participants.
*
* @param {Object} ctx - The context.
* @param {IJoinOptions} options - The options to join.
*/
export async function ensureTwoParticipants(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
await joinTheModeratorAsP1(ctx, options);

const { skipInMeetingChecks } = options;

await Promise.all([
_joinParticipant('participant2', context.p2, p => {
context.p2 = p;
_joinParticipant('participant2', ctx.p2, p => {
ctx.p2 = p;
}, options),
context.p1.waitForRemoteStreams(1),
context.p2.waitForRemoteStreams(1)
skipInMeetingChecks ? Promise.resolve() : ctx.p1.waitForRemoteStreams(1),
skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(1)
]);
}

Expand All @@ -132,15 +109,15 @@ async function _joinParticipant( // eslint-disable-line max-params
options: IJoinOptions = {},
jwtToken?: string) {
if (p) {
if (context.iframeAPI) {
if (ctx.iframeAPI) {
await p.switchInPage();
}

if (await p.isInMuc()) {
return;
}

if (context.iframeAPI) {
if (ctx.iframeAPI) {
// when loading url make sure we are on the top page context or strange errors may occur
await p.switchToAPI();
}
Expand All @@ -156,7 +133,7 @@ async function _joinParticipant( // eslint-disable-line max-params
// set the new participant instance, pass it to setter
setter(newParticipant);

await newParticipant.joinConference(context, options);
await newParticipant.joinConference(ctx, options);
}

/**
Expand All @@ -176,16 +153,28 @@ export async function muteAudioAndCheck(testee: Participant, observer: Participa
await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee);
}

/**
* Unmute audio, checks if the local UI has been updated accordingly and then does the verification from
* the other observer participant perspective.
* @param testee
* @param observer
*/
export async function unmuteAudioAndCheck(testee: Participant, observer: Participant) {
await testee.getToolbar().clickAudioUnmuteButton();
await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true);
await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, true);
}

/**
* Starts the video on testee and check on observer.
* @param testee
* @param observer
*/
export async function unMuteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
export async function unmuteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
await testee.getToolbar().clickVideoUnmuteButton();

await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
}

/**
Expand All @@ -206,7 +195,7 @@ function getModeratorToken(displayName: string) {
return;
}

const key = fs.readFileSync(context.jwtPrivateKeyPath);
const key = fs.readFileSync(ctx.jwtPrivateKeyPath);

const payload = {
'aud': 'jitsi',
Expand Down
6 changes: 6 additions & 0 deletions tests/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type IContext = {
p3: Participant;
p4: Participant;
roomName: string;
skipSuiteTests: boolean;
webhooksProxy: WebhookProxy;
};

Expand All @@ -21,6 +22,11 @@ export type IJoinOptions = {
*/
skipDisplayName?: boolean;

/**
* Whether to skip setting the moderator role for the first participant (whether to use jwt for it).
*/
skipFirstModerator?: boolean;

/**
* Whether to skip in meeting checks like ice connected and send receive data. For single in meeting participant.
*/
Expand Down
Loading
Loading