snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
330
tests/helpers/participants.ts
Normal file
330
tests/helpers/participants.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
import { P1, P2, P3, P4, P5, P6, Participant } from './Participant';
|
||||
import { config } from './TestsConfig';
|
||||
import { IJoinOptions, IParticipantOptions } from './types';
|
||||
|
||||
const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';
|
||||
|
||||
/**
|
||||
* Ensure that there is on participant.
|
||||
* Ensure that the first participant is moderator if there is such an option.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @param participantOptions
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function ensureOneParticipant(
|
||||
options?: IJoinOptions, participantOptions?: IParticipantOptions): Promise<void> {
|
||||
if (!participantOptions) {
|
||||
participantOptions = { name: P1 };
|
||||
}
|
||||
participantOptions.name = P1;
|
||||
|
||||
if (!participantOptions.token) {
|
||||
participantOptions.token = config.jwt.preconfiguredToken;
|
||||
}
|
||||
|
||||
// make sure the first participant is moderator, if supported by deployment
|
||||
await joinParticipant(participantOptions, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there are three participants.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function ensureThreeParticipants(options?: IJoinOptions): Promise<void> {
|
||||
await ensureOneParticipant(options);
|
||||
|
||||
// these need to be all, so we get the error when one fails
|
||||
await Promise.all([
|
||||
joinParticipant({ name: P2 }, options),
|
||||
joinParticipant({ name: P3 }, options)
|
||||
]);
|
||||
|
||||
if (options?.skipInMeetingChecks) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
ctx.p1.waitForIceConnected(),
|
||||
ctx.p2.waitForIceConnected(),
|
||||
ctx.p3.waitForIceConnected()
|
||||
]);
|
||||
await Promise.all([
|
||||
ctx.p1.waitForSendReceiveData().then(() => ctx.p1.waitForRemoteStreams(1)),
|
||||
ctx.p2.waitForSendReceiveData().then(() => ctx.p2.waitForRemoteStreams(1)),
|
||||
ctx.p3.waitForSendReceiveData().then(() => ctx.p3.waitForRemoteStreams(1)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the first participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function joinFirstParticipant(options: IJoinOptions = { }): Promise<void> {
|
||||
return ensureOneParticipant(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the second participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<Participant>}
|
||||
*/
|
||||
export function joinSecondParticipant(options?: IJoinOptions): Promise<Participant> {
|
||||
return joinParticipant({ name: P2 }, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the third participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<Participant>}
|
||||
*/
|
||||
export function joinThirdParticipant(options?: IJoinOptions): Promise<Participant> {
|
||||
return joinParticipant({ name: P3 }, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there are four participants.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function ensureFourParticipants(options?: IJoinOptions): Promise<void> {
|
||||
await ensureOneParticipant(options);
|
||||
|
||||
// these need to be all, so we get the error when one fails
|
||||
await Promise.all([
|
||||
joinParticipant({ name: P2 }, options),
|
||||
joinParticipant({ name: P3 }, options),
|
||||
joinParticipant({ name: P4 }, options)
|
||||
]);
|
||||
|
||||
if (options?.skipInMeetingChecks) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
ctx.p1.waitForIceConnected(),
|
||||
ctx.p2.waitForIceConnected(),
|
||||
ctx.p3.waitForIceConnected(),
|
||||
ctx.p4.waitForIceConnected()
|
||||
]);
|
||||
await Promise.all([
|
||||
ctx.p1.waitForSendReceiveData().then(() => ctx.p1.waitForRemoteStreams(1)),
|
||||
ctx.p2.waitForSendReceiveData().then(() => ctx.p2.waitForRemoteStreams(1)),
|
||||
ctx.p3.waitForSendReceiveData().then(() => ctx.p3.waitForRemoteStreams(1)),
|
||||
ctx.p4.waitForSendReceiveData().then(() => ctx.p4.waitForRemoteStreams(1)),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there are six participants.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function ensureSixParticipants(options?: IJoinOptions): Promise<void> {
|
||||
await ensureOneParticipant(options);
|
||||
|
||||
// Join participants in batches
|
||||
await Promise.all([
|
||||
joinParticipant({ name: P2 }, options),
|
||||
joinParticipant({ name: P3 }, options),
|
||||
joinParticipant({ name: P4 }, options)
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
joinParticipant({ name: P5 }, options),
|
||||
joinParticipant({ name: P6 }, options)
|
||||
]);
|
||||
|
||||
if (options?.skipInMeetingChecks) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
ctx.p1.waitForIceConnected(),
|
||||
ctx.p2.waitForIceConnected(),
|
||||
ctx.p3.waitForIceConnected(),
|
||||
ctx.p4.waitForIceConnected(),
|
||||
ctx.p5.waitForIceConnected(),
|
||||
ctx.p6.waitForIceConnected()
|
||||
]);
|
||||
await Promise.all([
|
||||
ctx.p1.waitForSendReceiveData().then(() => ctx.p1.waitForRemoteStreams(1)),
|
||||
ctx.p2.waitForSendReceiveData().then(() => ctx.p2.waitForRemoteStreams(1)),
|
||||
ctx.p3.waitForSendReceiveData().then(() => ctx.p3.waitForRemoteStreams(1)),
|
||||
ctx.p4.waitForSendReceiveData().then(() => ctx.p4.waitForRemoteStreams(1)),
|
||||
ctx.p5.waitForSendReceiveData().then(() => ctx.p5.waitForRemoteStreams(1)),
|
||||
ctx.p6.waitForSendReceiveData().then(() => ctx.p6.waitForRemoteStreams(1))
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that there are two participants.
|
||||
*
|
||||
* @param {IJoinOptions} options - The options to join.
|
||||
* @param participantOptions
|
||||
*/
|
||||
export async function ensureTwoParticipants(
|
||||
options?: IJoinOptions, participantOptions?: IParticipantOptions): Promise<void> {
|
||||
await ensureOneParticipant(options, participantOptions);
|
||||
|
||||
if (!participantOptions) {
|
||||
participantOptions = { name: P2 };
|
||||
}
|
||||
participantOptions.name = P2;
|
||||
|
||||
await joinParticipant(participantOptions, options);
|
||||
|
||||
if (options?.skipInMeetingChecks) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
ctx.p1.waitForIceConnected(),
|
||||
ctx.p2.waitForIceConnected()
|
||||
]);
|
||||
await Promise.all([
|
||||
ctx.p1.waitForSendReceiveData().then(() => ctx.p1.waitForRemoteStreams(1)),
|
||||
ctx.p2.waitForSendReceiveData().then(() => ctx.p2.waitForRemoteStreams(1))
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new participant instance, or returns an existing one if it is already joined.
|
||||
* @param participantOptions - The participant options, with required name set.
|
||||
* @param options - Join options.
|
||||
* @returns {Promise<Participant>} - The participant instance.
|
||||
*/
|
||||
async function joinParticipant( // eslint-disable-line max-params
|
||||
participantOptions: IParticipantOptions,
|
||||
options?: IJoinOptions
|
||||
): Promise<Participant> {
|
||||
|
||||
// @ts-ignore
|
||||
const p = ctx[participantOptions.name] as Participant;
|
||||
|
||||
if (p) {
|
||||
// Skip the isInMuc check (and the iframe context switch it requires) when the participant
|
||||
// is already on base.html — they cannot be in the MUC and there is no iframe to switch into.
|
||||
const alreadyOnBasePage = (await p.driver.getUrl()).endsWith('/base.html');
|
||||
|
||||
if (!alreadyOnBasePage) {
|
||||
if (participantOptions.iFrameApi) {
|
||||
await p.switchToIFrame();
|
||||
}
|
||||
|
||||
if (await p.isInMuc()) {
|
||||
return p;
|
||||
}
|
||||
|
||||
if (participantOptions.iFrameApi) {
|
||||
// when loading url make sure we are on the top page context or strange errors may occur
|
||||
await p.switchToMainFrame();
|
||||
}
|
||||
|
||||
// Change the page so we can reload same url if we need to, base.html is supposed to be empty or close to empty
|
||||
await p.driver.url('/base.html');
|
||||
}
|
||||
}
|
||||
|
||||
const newParticipant = new Participant(participantOptions);
|
||||
|
||||
// set the new participant instance
|
||||
// @ts-ignore
|
||||
ctx[participantOptions.name] = newParticipant;
|
||||
|
||||
return await newParticipant.joinConference({
|
||||
...options,
|
||||
roomName: options?.roomName || ctx.roomName,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JID string.
|
||||
* @param str the string to parse.
|
||||
*/
|
||||
export function parseJid(str: string): {
|
||||
domain: string;
|
||||
node: string;
|
||||
resource: string | undefined;
|
||||
} {
|
||||
const parts = str.split('@');
|
||||
const domainParts = parts[1].split('/');
|
||||
|
||||
return {
|
||||
node: parts[0],
|
||||
domain: domainParts[0],
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a screensharing tile is displayed on the observer.
|
||||
* Expects there was already a video by this participant and screen sharing will be the second video `-v1`.
|
||||
*/
|
||||
export async function checkForScreensharingTile(sharer: Participant, observer: Participant, reverse = false) {
|
||||
const selector = `//span[@id='participant_${await sharer.getEndpointId()}-v1']`;
|
||||
|
||||
// Re-runs the selector on every poll, unlike waitForDisplayed(), which keeps checking the element it
|
||||
// matched first. Thumbnails get replaced by React while the wait is running, and losing the matched one
|
||||
// is not something wdio recovers from here: over WebDriver BiDi the node handle is passed to the
|
||||
// visibility check as a script argument, so Chrome rejects it with `invalid argument - Invalid input in
|
||||
// "arguments"/0`, which wdio's refetch-on-stale handling does not recognize as staleness. Every
|
||||
// remaining poll then reuses the dead handle, failing the wait while a tile is on screen.
|
||||
await observer.driver.waitUntil(
|
||||
async () => {
|
||||
try {
|
||||
const displayed = await observer.driver.$(selector).isDisplayed();
|
||||
|
||||
return displayed !== reverse;
|
||||
} catch (e) {
|
||||
// Look the element up again on the next poll.
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
timeout: 3_000,
|
||||
timeoutMsg: `Screensharing tile of ${sharer.name} is ${
|
||||
reverse ? 'still displayed' : 'not displayed'} on ${observer.name}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hangs up all participants (p1, p2, p3, p4, p5, and p6)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function hangupAllParticipants() {
|
||||
return Promise.all([
|
||||
ctx.p1?.hangup(),
|
||||
ctx.p2?.hangup(),
|
||||
ctx.p3?.hangup(),
|
||||
ctx.p4?.hangup(),
|
||||
ctx.p5?.hangup(),
|
||||
ctx.p6?.hangup()
|
||||
].map(p => p ?? Promise.resolve()));
|
||||
}
|
||||
Reference in New Issue
Block a user