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 chat panel and codec selection tests. #15416

Merged
merged 3 commits into from
Dec 20, 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
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
Loading