Skip to content

Commit

Permalink
feat(tests): Adds chat panel and codec selection tests. (#15416)
Browse files Browse the repository at this point in the history
* fix(tests): Another attempt to fix Firefox excludes.

Drawback is that it will be a little bit slow.

* feat(tests): Adds chatPanel tests.

* feat(tests): Adds codec selection tests.
  • Loading branch information
damencho authored Dec 20, 2024
1 parent aca5517 commit a30958a
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 16 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
"@types/js-md5": "0.4.3",
"@types/jsonwebtoken": "9.0.7",
"@types/lodash-es": "4.17.12",
"@types/minimatch": "5.1.2",
"@types/mocha": "10.0.10",
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
Expand Down
13 changes: 12 additions & 1 deletion tests/helpers/Participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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 ChatPanel from '../pageobjects/ChatPanel';
import Filmstrip from '../pageobjects/Filmstrip';
import IframeAPI from '../pageobjects/IframeAPI';
import Notifications from '../pageobjects/Notifications';
Expand Down Expand Up @@ -121,7 +122,10 @@ export class Participant {
async joinConference(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
const config = {
room: ctx.roomName,
configOverwrite: this.config,
configOverwrite: {
...this.config,
...options.configOverwrite || {}
},
interfaceConfigOverwrite: {
SHOW_CHROME_EXTENSION_BANNER: false
}
Expand Down Expand Up @@ -338,6 +342,13 @@ export class Participant {
});
}

/**
* Returns the chat panel for this participant.
*/
getChatPanel(): ChatPanel {
return new ChatPanel(this);
}

/**
* Returns the BreakoutRooms for this participant.
*
Expand Down
23 changes: 11 additions & 12 deletions tests/helpers/participants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,25 @@ export async function ensureOneParticipant(ctx: IContext, options?: IJoinOptions
* Ensure that there are three participants.
*
* @param {Object} ctx - The context.
* @param {IJoinOptions} options - The options to use when joining the participant.
* @returns {Promise<void>}
*/
export async function ensureThreeParticipants(ctx: IContext): Promise<void> {
await joinTheModeratorAsP1(ctx);

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

ctx.p2 = p2;
ctx.p3 = p3;
export async function ensureThreeParticipants(ctx: IContext, options?: IJoinOptions): Promise<void> {
await joinTheModeratorAsP1(ctx, options);

// these need to be all, so we get the error when one fails
await Promise.all([
p2.joinConference(ctx),
p3.joinConference(ctx)
_joinParticipant('participant2', ctx.p2, p => {
ctx.p2 = p;
}, options),
_joinParticipant('participant3', ctx.p3, p => {
ctx.p3 = p;
}, options)
]);

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

Expand Down
7 changes: 7 additions & 0 deletions tests/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { IConfig } from '../../react/features/base/config/configType';

import type { Participant } from './Participant';
import WebhookProxy from './WebhookProxy';

Expand All @@ -17,6 +19,11 @@ export type IContext = {

export type IJoinOptions = {

/**
* Config overwrites to use.
*/
configOverwrite?: IConfig;

/**
* Whether to skip setting display name.
*/
Expand Down
22 changes: 22 additions & 0 deletions tests/pageobjects/ChatPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import BasePageObject from './BasePageObject';

/**
* Chat panel elements.
*/
export default class ChatPanel extends BasePageObject {
/**
* Is chat panel open.
*/
async isOpen() {
return await this.participant.driver.$('#sideToolbarContainer').isExisting();
}

/**
* Presses the "chat" keyboard shortcut which opens or closes the chat
* panel.
*/
async pressShortcut() {
await this.participant.driver.$('body').click();
await this.participant.driver.keys([ 'c' ]);
}
}
18 changes: 18 additions & 0 deletions tests/pageobjects/Toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import BasePageObject from './BasePageObject';

const AUDIO_MUTE = 'Mute microphone';
const AUDIO_UNMUTE = 'Unmute microphone';
const CHAT = 'Open chat';
const CLOSE_CHAT = 'Close chat';
const CLOSE_PARTICIPANTS_PANE = 'Close participants pane';
const OVERFLOW_MENU = 'More actions menu';
const OVERFLOW = 'More actions';
Expand Down Expand Up @@ -142,6 +144,22 @@ export default class Toolbar extends BasePageObject {
await this.getButton(RAISE_HAND).click();
}

/**
* Clicks on the chat button that opens chat panel.
*/
async clickChatButton(): Promise<void> {
this.participant.log('Clicking on: Chat Button');
await this.getButton(CHAT).click();
}

/**
* Clicks on the chat button that closes chat panel.
*/
async clickCloseChatButton(): Promise<void> {
this.participant.log('Clicking on: Close Chat Button');
await this.getButton(CLOSE_CHAT).click();
}

/**
* Ensure the overflow menu is open and clicks on a specified button.
* @param accessibilityLabel The accessibility label of the button to be clicked.
Expand Down
121 changes: 121 additions & 0 deletions tests/specs/3way/codecSelection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ensureOneParticipant, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';

describe('Codec selection - ', () => {
it('asymmetric codecs', async () => {
await ensureOneParticipant(ctx, {
configOverwrite: {
videoQuality: {
codecPreferenceOrder: [ 'VP9', 'VP8', 'AV1' ]
}
}
});

await ensureTwoParticipants(ctx, {
configOverwrite: {
videoQuality: {
codecPreferenceOrder: [ 'VP8', 'VP9', 'AV1' ]
}
}
});
const { p1, p2 } = ctx;

// Check if media is playing on both endpoints.
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
expect(await p2.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);

// Check if p1 is sending VP9 and p2 is sending VP8 as per their codec preferences.
// Except on Firefox because it doesn't support VP9 encode.
if (p1.driver.isFirefox) {
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
} else {
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
}

expect(await p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
});

it('asymmetric codecs with AV1', async () => {
await ensureThreeParticipants(ctx, {
configOverwrite: {
videoQuality: {
codecPreferenceOrder: [ 'AV1', 'VP9', 'VP8' ]
}
}
});
const { p1, p2, p3 } = ctx;

// Check if media is playing on p3.
expect(await p3.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);

// Check if p1 is encoding in VP9, p2 in VP8 and p3 in AV1 as per their codec preferences.
// Except on Firefox because it doesn't support AV1/VP9 encode and AV1 decode.
if (p1.driver.isFirefox) {
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
} else {
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
}

expect(await p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);

// If there is a Firefox ep in the call, all other eps will switch to VP9.
if (p1.driver.isFirefox) {
expect(await p3.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
} else {
expect(await p3.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingAv1())).toBe(true);
}
});

it('codec switch over', async () => {
await Promise.all([ ctx.p1.hangup(), ctx.p2.hangup(), ctx.p3.hangup() ]);

await ensureTwoParticipants(ctx, {
configOverwrite: {
videoQuality: {
codecPreferenceOrder: [ 'VP9', 'VP8', 'AV1' ]
}
}
});
const { p1, p2 } = ctx;

// Disable this test on Firefox because it doesn't support VP9 encode.
if (p1.driver.isFirefox) {
return;
}

// Check if p1 and p2 are encoding in VP9 which is the default codec.
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);
expect(await p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9())).toBe(true);

await ensureThreeParticipants(ctx, {
configOverwrite: {
videoQuality: {
codecPreferenceOrder: [ 'VP8' ]
}
}
});
const { p3 } = ctx;

// Check if all three participants are encoding in VP8 now.
expect(await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
expect(await p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);
expect(await p3.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp8())).toBe(true);

await p3.hangup();

// Check of p1 and p2 have switched to VP9.
await p1.driver.waitUntil(
async () => await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
{
timeout: 10000,
timeoutMsg: 'p1 did not switch back to VP9'
}
);
await p2.driver.waitUntil(
async () => await p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
{
timeout: 10000,
timeoutMsg: 'p1 did not switch back to VP9'
}
);
});
});
34 changes: 34 additions & 0 deletions tests/specs/alone/chatPanel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ensureOneParticipant } from '../../helpers/participants';

describe('Chat Panel - ', () => {
it('join participant', async () => {
await ensureOneParticipant(ctx);
});
it('start closed', async () => {
expect(await ctx.p1.getChatPanel().isOpen()).toBe(false);
});
it('open', async () => {
const { p1 } = ctx;

await p1.getToolbar().clickChatButton();
expect(await p1.getChatPanel().isOpen()).toBe(true);
});
it('use shortcut to close', async () => {
const chatPanel = ctx.p1.getChatPanel();

await chatPanel.pressShortcut();
expect(await chatPanel.isOpen()).toBe(false);
});
it('use shortcut to open', async () => {
const chatPanel = ctx.p1.getChatPanel();

await chatPanel.pressShortcut();
expect(await chatPanel.isOpen()).toBe(true);
});
it('use button to open', async () => {
const { p1 } = ctx;

await p1.getToolbar().clickCloseChatButton();
expect(await p1.getChatPanel().isOpen()).toBe(false);
});
});
28 changes: 26 additions & 2 deletions tests/wdio.conf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AllureReporter from '@wdio/allure-reporter';
import { multiremotebrowser } from '@wdio/globals';
import { Buffer } from 'buffer';
import minimatch from 'minimatch';
import path from 'node:path';
import process from 'node:process';
import pretty from 'pretty';
Expand Down Expand Up @@ -62,7 +63,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
specs: [
'specs/**'
],
maxInstances: 1,
maxInstances: 1, // if changing check onWorkerStart logic

baseUrl: process.env.BASE_URL || 'https://alpha.jitsi.net/torture/',
tsConfigPath: './tsconfig.json',
Expand Down Expand Up @@ -236,13 +237,36 @@ export const config: WebdriverIO.MultiremoteConfig = {
* @param {Object} context - The context object.
*/
beforeTest(test, context) {
ctx.skipSuiteTests && context.skip();
if (ctx.skipSuiteTests) {
context.skip();

return;
}

multiremotebrowser.instances.forEach((instance: string) => {
logInfo(multiremotebrowser.getInstance(instance), `---=== Start test ${test.title} ===---`);
});
},

/**
* Gets executed before a worker process is spawned and can be used to initialize specific service
* for that worker as well as modify runtime environments in an async fashion.
*/
onWorkerStart(...args) {
// We run a worker per suite, and replay on this logic here
if (args[2].length > 1) {
console.warn('Our worker is supposed to get a single suite, but got more than one');

return;
}

// We skip the suite tests if the suite is marked as such, we used that from firefox overwrite
// @ts-ignore
if (config?.ffExcludes.some((e: string) => minimatch(args[2][0].replace('file://', ''), `${__dirname}/${e}`))) {
args[2].pop();
}
},

/**
* Function to be executed after a test (in Mocha/Jasmine only).
*
Expand Down
Loading

0 comments on commit a30958a

Please sign in to comment.