mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-10-04 01:46:59 +00:00
Compare commits
32 Commits
8367
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b5b1d01b1 | ||
|
|
0d109eaf00 | ||
|
|
75c43fc8f2 | ||
|
|
532b5858c5 | ||
|
|
edc2328fd6 | ||
|
|
a603a8df06 | ||
|
|
703a4d0f34 | ||
|
|
44c1624c61 | ||
|
|
5b5e233734 | ||
|
|
5607e50b8e | ||
|
|
39c942d585 | ||
|
|
1c04f974d8 | ||
|
|
dc5142cb6b | ||
|
|
d563913499 | ||
|
|
4d0642d1a7 | ||
|
|
8d8e83cdfd | ||
|
|
6d0271338e | ||
|
|
1401eb245e | ||
|
|
27eec7b19e | ||
|
|
b0d0b3ac11 | ||
|
|
2c11b1b945 | ||
|
|
2c92ea57f0 | ||
|
|
09b696e95a | ||
|
|
d67054a550 | ||
|
|
05374e292b | ||
|
|
f038099e61 | ||
|
|
da5f2d00d4 | ||
|
|
c8a7877fdd | ||
|
|
d8da660070 | ||
|
|
4e033fff4b | ||
|
|
4861f95368 | ||
|
|
b3ee8fe127 |
8
Makefile
8
Makefile
@@ -17,11 +17,11 @@ STYLES_BUNDLE = css/all.bundle.css
|
||||
STYLES_DESTINATION = css/all.css
|
||||
STYLES_MAIN = css/main.scss
|
||||
ifeq ($(OS),Windows_NT)
|
||||
WEBPACK = .\node_modules\.bin\webpack
|
||||
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development
|
||||
WEBPACK = .\node_modules\.bin\webpack --progress
|
||||
WEBPACK_DEV_SERVER = .\node_modules\.bin\webpack serve --mode development --progress
|
||||
else
|
||||
WEBPACK = ./node_modules/.bin/webpack
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
|
||||
WEBPACK = ./node_modules/.bin/webpack --progress
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development --progress
|
||||
endif
|
||||
|
||||
all: compile deploy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# <p align="center">Jitsi Meet</p>
|
||||
|
||||
Jitsi Meet is a set of Open Source projects which empower users to use and deploy
|
||||
TEST. Jitsi Meet is a set of Open Source projects which empower users to use and deploy
|
||||
video conferencing platforms with state-of-the-art video quality and features.
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -136,6 +136,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
private String userSelectedDevice;
|
||||
|
||||
/**
|
||||
* Whether or not audio is disabled.
|
||||
*/
|
||||
private boolean audioDisabled;
|
||||
|
||||
/**
|
||||
* Initializes a new module instance. There shall be a single instance of
|
||||
* this module throughout the lifetime of the application.
|
||||
@@ -236,6 +241,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
audioDeviceHandler.stop();
|
||||
}
|
||||
|
||||
audioDeviceHandler = null;
|
||||
|
||||
if (audioDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (useConnectionService()) {
|
||||
audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager);
|
||||
} else {
|
||||
@@ -278,6 +289,27 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setDisabled(final boolean disabled, final Promise promise) {
|
||||
if (audioDisabled == disabled) {
|
||||
promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
JitsiMeetLogger.i(TAG + " audio disabled: " + disabled);
|
||||
|
||||
audioDisabled = disabled;
|
||||
setAudioDeviceHandler();
|
||||
|
||||
if (disabled) {
|
||||
mode = -1;
|
||||
availableDevices.clear();
|
||||
resetSelectedDevice();
|
||||
}
|
||||
|
||||
promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to set the current audio mode.
|
||||
*
|
||||
@@ -287,7 +319,12 @@ class AudioModeModule extends ReactContextBaseJavaModule {
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setMode(final int mode, final Promise promise) {
|
||||
if (mode != DEFAULT && mode != AUDIO_CALL && mode != VIDEO_CALL) {
|
||||
if (audioDisabled) {
|
||||
promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode < DEFAULT || mode > VIDEO_CALL) {
|
||||
promise.reject("setMode", "Invalid audio mode " + mode);
|
||||
return;
|
||||
}
|
||||
|
||||
6
app.js
6
app.js
@@ -1,4 +1,8 @@
|
||||
/* application specific logic */
|
||||
/* Jitsi Meet app main entrypoint. */
|
||||
|
||||
// Polyfill Promise.withReolvers.
|
||||
// FIXME(saghul) webpack + core-js v3 should polyfill this.
|
||||
import 'promise.withresolvers/auto';
|
||||
|
||||
// Re-export jQuery
|
||||
// FIXME: Remove this requirement from torture tests.
|
||||
|
||||
52
config.js
52
config.js
@@ -84,6 +84,9 @@ var config = {
|
||||
// Allows the setting of a custom bandwidth value from the UI.
|
||||
// assumeBandwidth: true,
|
||||
|
||||
// Enables use of getDisplayMedia in electron
|
||||
// electronUseGetDisplayMedia: false,
|
||||
|
||||
// Enables the use of the codec selection API supported by the browsers .
|
||||
// enableCodecSelectionAPI: false,
|
||||
|
||||
@@ -573,30 +576,6 @@ var config = {
|
||||
// useKSVC: true
|
||||
// },
|
||||
//
|
||||
// DEPRECATED! Use `codec specific settings` instead.
|
||||
// // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for
|
||||
// // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values
|
||||
// // are the max.bitrates to be set on that particular type of stream. The actual send may vary based on
|
||||
// // the available bandwidth calculated by the browser, but it will be capped by the values specified here.
|
||||
// // This is currently not implemented on app based clients on mobile.
|
||||
// maxBitratesVideo: {
|
||||
// H264: {
|
||||
// low: 200000,
|
||||
// standard: 500000,
|
||||
// high: 1500000,
|
||||
// },
|
||||
// VP8 : {
|
||||
// low: 200000,
|
||||
// standard: 500000,
|
||||
// high: 1500000,
|
||||
// },
|
||||
// VP9: {
|
||||
// low: 100000,
|
||||
// standard: 300000,
|
||||
// high: 1200000,
|
||||
// },
|
||||
// },
|
||||
//
|
||||
// // The options can be used to override default thresholds of video thumbnail heights corresponding to
|
||||
// // the video quality levels used in the application. At the time of this writing the allowed levels are:
|
||||
// // 'low' - for the low quality level (180p at the time of this writing)
|
||||
@@ -614,22 +593,6 @@ var config = {
|
||||
//
|
||||
// // Provides a way to set the codec preference on mobile devices, both on RN and mobile browser based endpoint
|
||||
// mobileCodecPreferenceOrder: [ 'VP8', 'VP9', 'H264' ],
|
||||
//
|
||||
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
|
||||
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
|
||||
// // here will be removed from the list of codecs present in the SDP answer generated by the client. If the
|
||||
// // same codec is specified for both the disabled and preferred option, the disable settings will prevail.
|
||||
// // Note that 'VP8' cannot be disabled since it's a mandatory codec, the setting will be ignored in this case.
|
||||
// disabledCodec: 'H264',
|
||||
//
|
||||
// // DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
|
||||
// // Provides a way to set a preferred video codec for the JVB connection. If 'H264' is specified here,
|
||||
// // simulcast will be automatically disabled since JVB doesn't support H264 simulcast yet. This will only
|
||||
// // rearrange the the preference order of the codecs in the SDP answer generated by the browser only if the
|
||||
// // preferred codec specified here is present. Please ensure that the JVB offers the specified codec for this
|
||||
// // to take effect.
|
||||
// preferredCodec: 'VP8',
|
||||
//
|
||||
// },
|
||||
|
||||
// Notification timeouts
|
||||
@@ -1122,15 +1085,6 @@ var config = {
|
||||
// { urls: 'stun:jitsi-meet.example.com:3478' },
|
||||
{ urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' },
|
||||
],
|
||||
|
||||
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
|
||||
// Provides a way to set the video codec preference on the p2p connection. Acceptable
|
||||
// codec values are 'VP8', 'VP9' and 'H264'.
|
||||
// preferredCodec: 'H264',
|
||||
|
||||
// DEPRECATED! Use `codecPreferenceOrder/mobileCodecPreferenceOrder` instead.
|
||||
// Provides a way to prevent a video codec from being negotiated on the p2p connection.
|
||||
// disabledCodec: '',
|
||||
},
|
||||
|
||||
analytics: {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#import <WebRTC/WebRTC.h>
|
||||
|
||||
#import "JitsiAudioSession+Private.h"
|
||||
#import "callkit/JMCallKitProxy.h"
|
||||
|
||||
|
||||
// Audio mode
|
||||
@@ -54,6 +55,7 @@ static NSString * const kDeviceTypeUnknown = @"UNKNOWN";
|
||||
RTCAudioSessionConfiguration *audioCallConfig;
|
||||
RTCAudioSessionConfiguration *videoCallConfig;
|
||||
RTCAudioSessionConfiguration *earpieceConfig;
|
||||
BOOL audioDisabled;
|
||||
BOOL forceSpeaker;
|
||||
BOOL forceEarpiece;
|
||||
BOOL isSpeakerOn;
|
||||
@@ -146,9 +148,36 @@ RCT_EXPORT_MODULE();
|
||||
|
||||
#pragma mark - Exported methods
|
||||
|
||||
RCT_EXPORT_METHOD(setDisabled:(BOOL)disabled
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
if (audioDisabled == disabled) {
|
||||
resolve(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTLogInfo(@"[AudioMode] audio disabled: %d", disabled);
|
||||
|
||||
audioDisabled = disabled;
|
||||
JMCallKitProxy.enabled = !disabled;
|
||||
|
||||
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
|
||||
if (disabled) {
|
||||
[session removeDelegate:self];
|
||||
} else {
|
||||
[session addDelegate:self];
|
||||
}
|
||||
session.useManualAudio = disabled;
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setMode:(int)mode
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
if (audioDisabled) {
|
||||
resolve(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
RTCAudioSessionConfiguration *config = [self configForMode:mode];
|
||||
NSError *error;
|
||||
|
||||
@@ -177,6 +206,11 @@ RCT_EXPORT_METHOD(setMode:(int)mode
|
||||
RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject) {
|
||||
if (audioDisabled) {
|
||||
resolve(nil);
|
||||
return;
|
||||
}
|
||||
|
||||
RCTLogInfo(@"[AudioMode] Selected device: %@", device);
|
||||
|
||||
RTCAudioSession *session = JitsiAudioSession.rtcAudioSession;
|
||||
@@ -239,6 +273,10 @@ RCT_EXPORT_METHOD(setAudioDevice:(NSString *)device
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(updateDeviceList) {
|
||||
if (audioDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self notifyDevicesChanged];
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,15 @@
|
||||
}
|
||||
|
||||
+ (void)activateWithAudioSession:(AVAudioSession *)session {
|
||||
[self.rtcAudioSession audioSessionDidActivate:session];
|
||||
if (!self.rtcAudioSession.useManualAudio) {
|
||||
[self.rtcAudioSession audioSessionDidActivate:session];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)deactivateWithAudioSession:(AVAudioSession *)session {
|
||||
[self.rtcAudioSession audioSessionDidDeactivate:session];
|
||||
if (!self.rtcAudioSession.useManualAudio) {
|
||||
[self.rtcAudioSession audioSessionDidDeactivate:session];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"accessibilityLabel": {
|
||||
"meetingLink": "Mötes länk: {{url}}"
|
||||
},
|
||||
"add": "Bjud in",
|
||||
"addContacts": "Bjud in dina kontakter",
|
||||
"contacts": "Kontakter",
|
||||
@@ -39,6 +42,18 @@
|
||||
"audioOnly": {
|
||||
"audioOnly": "Enbart ljud"
|
||||
},
|
||||
"bandwidthSettings": {
|
||||
"assumedBandwidthBps": "t.ex. 10000000 för 10 Mbps",
|
||||
"assumedBandwidthBpsWarning": "Högre värden kan skapa nätverksproblem.",
|
||||
"customValue": "anpassat värde",
|
||||
"customValueEffect": "för att ställa in det faktiska bps-värdet",
|
||||
"leaveEmpty": "lämna tomt",
|
||||
"leaveEmptyEffect": "för att uppskattningar ska äga rum",
|
||||
"possibleValues": "Möjliga värden",
|
||||
"setAssumedBandwidthBps": "Uppskattat värde (bps)",
|
||||
"title": "Bandbreddsinställningar",
|
||||
"zeroEffect": "för att slå av video"
|
||||
},
|
||||
"breakoutRooms": {
|
||||
"actions": {
|
||||
"add": "Lägg till grupprum",
|
||||
@@ -48,15 +63,22 @@
|
||||
"leaveBreakoutRoom": "Lämna grupprum",
|
||||
"more": "Mer",
|
||||
"remove": "Ta bort",
|
||||
"rename": "Döp om",
|
||||
"renameBreakoutRoom": "Döp om grupprum",
|
||||
"sendToBreakoutRoom": "Skicka deltagare till grupprum:"
|
||||
},
|
||||
"breakoutList": "Grupprums lista",
|
||||
"buttonLabel": "Grupprum",
|
||||
"defaultName": "Grupprum #{{index}}",
|
||||
"hideParticipantList": "Dölj deltagarlista",
|
||||
"mainRoom": "Huvudrum",
|
||||
"notifications": {
|
||||
"joined": "Gick med i \"{{name}}\"",
|
||||
"joined": "Gick med i grupprum \"{{name}}\"",
|
||||
"joinedMainRoom": "Gick med i huvudrummet",
|
||||
"joinedTitle": "Grupprum"
|
||||
}
|
||||
},
|
||||
"showParticipantList": "Visa deltagarlista",
|
||||
"title": "Grupprum"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Lägg till en möteslänk",
|
||||
@@ -106,6 +128,7 @@
|
||||
"privateNotice": "Privat meddelande till {{recipient}}",
|
||||
"sendButton": "Skicka",
|
||||
"smileysPanel": "Emoji panel",
|
||||
"systemDisplayName": "",
|
||||
"tabs": {
|
||||
"chat": "Chatt",
|
||||
"polls": "Omröstningar"
|
||||
@@ -197,7 +220,9 @@
|
||||
"joinInBrowser": "Delta på webben",
|
||||
"launchMeetingLabel": "Hur vill du delta i detta möte?",
|
||||
"launchWebButton": "Starta på webben",
|
||||
"noDesktopApp": "",
|
||||
"noMobileApp": "Har du inte appen?",
|
||||
"or": "",
|
||||
"termsAndConditions": "Genom att fortsätta godkänner du våra <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>villkor.</a>",
|
||||
"title": "Startar ditt möte i {{app}}…",
|
||||
"titleNew": "Startar ditt möte…",
|
||||
@@ -239,9 +264,13 @@
|
||||
"Share": "Dela",
|
||||
"Submit": "Skicka",
|
||||
"WaitForHostMsg": "Konferensen har inte börjat än. Autentisera konferensen om du är värd. Vänta annars på att värden startar konferensen.",
|
||||
"WaitForHostNoAuthMsg": "Konferensen har ännu inte startat eftersom ingen värd har anlänt ännu. Vänligen vänta.",
|
||||
"WaitingForHostButton": "Vänta på värd",
|
||||
"WaitingForHostTitle": "Väntar på värden…",
|
||||
"Yes": "Ja",
|
||||
"accessibilityLabel": {
|
||||
"Cancel": "Avbryt",
|
||||
"Ok": "Ok",
|
||||
"close": "Stäng",
|
||||
"liveStreaming": "Livesändning",
|
||||
"sharingTabs": "Delningsalternativ"
|
||||
@@ -250,6 +279,8 @@
|
||||
"addMeetingNote": "Mötesinformation",
|
||||
"addOptionalNote": "Ytterligare information",
|
||||
"allow": "Tillåt",
|
||||
"allowToggleCameraDialog": "Tillåter du att {{initiatorName}} får växla kamera till ansiktsläge?",
|
||||
"allowToggleCameraTitle": "Tillåt att växla kamera",
|
||||
"alreadySharedVideoMsg": "En annan deltagare delar redan en video. Konferensen tillåter bara en video-delning åt gången.",
|
||||
"alreadySharedVideoTitle": "Endast en delad video åt gången tillåts",
|
||||
"applicationWindow": "Applikationsfönster",
|
||||
@@ -276,6 +307,8 @@
|
||||
"contactSupport": "Kontakta kundtjänst",
|
||||
"copied": "Kopierad",
|
||||
"copy": "Kopiera",
|
||||
"demoteParticipantDialog": "Are you sure you want to move this participant to viewer? Är du säker på att du vill flytta denna deltagaren till tittare",
|
||||
"demoteParticipantTitle": "Flytta till tittare",
|
||||
"dismiss": "Förkasta",
|
||||
"displayNameRequired": "Hej, vad heter du?",
|
||||
"done": "Klar",
|
||||
@@ -287,6 +320,7 @@
|
||||
"embedMeeting": "Bädda in möte",
|
||||
"enterDisplayName": "Ange namn",
|
||||
"error": "Fel",
|
||||
"errorRoomCreationRestriction": "Du försökte gå med för snabbt, kom tillbaka om en stund.",
|
||||
"gracefulShutdown": "Vår tjänst är för tillfället nedstängd för underhåll. Vänligen försök senare.",
|
||||
"grantModeratorDialog": "Är du säker du vill göra denna deltagare till en moderator?",
|
||||
"grantModeratorTitle": "Godkänn moderator",
|
||||
@@ -300,6 +334,7 @@
|
||||
"kickParticipantButton": "Sparka ut",
|
||||
"kickParticipantDialog": "Vill du sparka ut den här deltagaren?",
|
||||
"kickParticipantTitle": "Tysta deltagaren?",
|
||||
"kickSystemTitle": "Aj! du blev utsparkad ur mötet",
|
||||
"kickTitle": "Aj! {{participantDisplayName}} sparkade ut dig ur mötet",
|
||||
"linkMeeting": "Länka möte",
|
||||
"linkMeetingTitle": "Länka möte till Salesforce",
|
||||
@@ -310,7 +345,8 @@
|
||||
"lockRoom": "Lägg till möte $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Låsning misslyckades",
|
||||
"login": "Logga in",
|
||||
"logoutQuestion": "Är du säker på att du vill logga ut och stoppa konferensen?",
|
||||
"loginQuestion": "Är du säker på att du vill logga in och lämna mötet",
|
||||
"logoutQuestion": "Är du säker på att du vill logga ut och lämna konferensen?",
|
||||
"logoutTitle": "Logga ut",
|
||||
"maxUsersLimitReached": "Gränsen för maximalt antal deltagare har nåtts. Konferensen är full. Kontakta mötesägaren eller försök igen senare!",
|
||||
"maxUsersLimitReachedTitle": "Maximal deltagarantal uppnått",
|
||||
@@ -353,8 +389,6 @@
|
||||
"permissionCameraRequiredError": "Tillåtelse krävs för att delta med kamera i denna möte. Var god skaffa detta i \"inställningar\".",
|
||||
"permissionErrorTitle": "Tillåtelse krävs",
|
||||
"permissionMicRequiredError": "Tillåtelse krävs för att delta med mikrofon i denna möte. Var god skaffa detta i \"inställningar\".",
|
||||
"popupError": "Din webbläsare blockerar pop-up-fönster från sajten. Tillåt pop-up-fönster från den här sajten i inställningarna och försök igen.",
|
||||
"popupErrorTitle": "Pop-up blockerad",
|
||||
"readMore": "Mer",
|
||||
"recentlyUsedObjects": "Dina senaste använda objekt",
|
||||
"recording": "Inspelning",
|
||||
@@ -371,6 +405,8 @@
|
||||
"removePassword": "Ta bort $t(lockRoomPassword)",
|
||||
"removeSharedVideoMsg": "Är du säker på att du vill ta bort din delade video?",
|
||||
"removeSharedVideoTitle": "Ta bort den delade videon",
|
||||
"renameBreakoutRoomLabel": "Rums namn",
|
||||
"renameBreakoutRoomTitle": "Döp om grupprum",
|
||||
"reservationError": "Fel i reservationssystemet",
|
||||
"reservationErrorMsg": "Felkod: {{code}}, meddelande: {{msg}}",
|
||||
"retry": "Försök igen",
|
||||
@@ -390,8 +426,10 @@
|
||||
"sendPrivateMessageTitle": "Skicka privat?",
|
||||
"serviceUnavailable": "Tjänsten otillgänglig",
|
||||
"sessTerminated": "Konferensen avslutades",
|
||||
"sessTerminatedReason": "",
|
||||
"sessionRestarted": "Samtal återstartat av bryggan",
|
||||
"shareAudio": "Fortsätt",
|
||||
"shareAudioAltText": "För att dela önskat innehåll, navigera till \"Webläsarfliken\", välj innehållet, aktivera \"dela ljud\"-kryssmärket och klicka sedan på \"dela\" knappen",
|
||||
"shareAudioTitle": "Hur man delar ljud",
|
||||
"shareAudioWarningD1": "Du måste avsluta din skärmdelning innan du kan dela ditt ljud",
|
||||
"shareAudioWarningD2": "Du måste starta om din skärmdelning och därefter klicka på \"ljuddelning\"",
|
||||
@@ -402,7 +440,10 @@
|
||||
"shareScreenWarningD2": "du måste avsluta ljuddelning, starta skärmdelning och sen aktivera \"ljuddelning\"",
|
||||
"shareScreenWarningH1": "Om du bara vill dela din skärm:",
|
||||
"shareScreenWarningTitle": "Du måste avsluta ljuddelning innan du kan dela in skärm",
|
||||
"shareVideoConfirmPlay": "Du är på väg att öppna en extern webbplats. Vill du fortsätta?",
|
||||
"shareVideoConfirmPlayTitle": "{{name}} har delat en video med dig.",
|
||||
"shareVideoLinkError": "Skriv in en fungerande länk.",
|
||||
"shareVideoLinkStopped": "Videon från {{name}} har avslutats.",
|
||||
"shareVideoTitle": "Ta bort en delad video",
|
||||
"shareYourScreen": "Dela din skärm",
|
||||
"shareYourScreenDisabled": "Skärmdelning har inaktiverats.",
|
||||
@@ -421,7 +462,25 @@
|
||||
"thankYou": "Tack för att du använder {{appName}}!",
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Du är inte behörig att delta i det här samtalet.",
|
||||
"tokenAuthFailedReason": {
|
||||
"audInvalid": "Ogiltigt `aud`-värde. Det ska vara `jitsi`.",
|
||||
"contextNotFound": "Objektet `context` saknas från payload.",
|
||||
"expInvalid": "Ogilting `exp` värde.",
|
||||
"featureInvalid": "Ogiltig feature: {{feature}}, mest troligt inte implementerat än.",
|
||||
"featureValueInvalid": "Felaktigt värde för feature: {{feature}}.",
|
||||
"featuresNotFound": "`Features` objektet saknas i payload.",
|
||||
"headerNotFound": "Saknar header.",
|
||||
"issInvalid": "Ogiltigt `iss`-värde. Det ska vara `chat`.",
|
||||
"kidMismatch": "Nyckel ID (kid) matchat inte sub.",
|
||||
"kidNotFound": "Nyckel ID saknas (kid).",
|
||||
"nbfFuture": "Värdet i `nbf` är i framtiden.",
|
||||
"nbfInvalid": "Ogilitigt `nbf`värde.",
|
||||
"payloadNotFound": "Payload saknas.",
|
||||
"tokenExpired": "Token har gått ut."
|
||||
},
|
||||
"tokenAuthFailedTitle": "Autentisering misslyckades",
|
||||
"tokenAuthFailedWithReasons": "Förlåt, du har inte tillåtelse att gå med i det här samtalet. Troliga anledingar: {{reason}}",
|
||||
"tokenAuthUnsupported": "Token URL är inte tillåten",
|
||||
"transcribing": "Transkriberar",
|
||||
"unlockRoom": "Ta bort möte $t(lockRoomPassword)",
|
||||
"user": "Användare",
|
||||
@@ -435,6 +494,10 @@
|
||||
"viewUpgradeOptions": "Se uppgraderings alternativ",
|
||||
"viewUpgradeOptionsContent": "För att få obegränsad tillgång till premiumfunktioner som inspelning, transkriptioner, RTMP-streaming och mer måste du uppgradera din plan.",
|
||||
"viewUpgradeOptionsTitle": "Du upptäckte en premiumfunktion!",
|
||||
"whiteboardLimitContent": "Förlåt, max antal samtidiga deltagare i whiteboard har uppnåts.",
|
||||
"whiteboardLimitReference": "För mer information besök",
|
||||
"whiteboardLimitReferenceUrl": "vår webplats",
|
||||
"whiteboardLimitTitle": "Användande av whiteboard har begränsats",
|
||||
"yourEntireScreen": "Helskärm"
|
||||
},
|
||||
"documentSharing": {
|
||||
@@ -447,6 +510,9 @@
|
||||
"title": "Bädda in möte"
|
||||
},
|
||||
"feedback": {
|
||||
"accessibilityLabel": {
|
||||
"yourChoice": "Ditt val: {{rating}}"
|
||||
},
|
||||
"average": "Medel",
|
||||
"bad": "Dåligt",
|
||||
"detailsLabel": "Berätta mer.",
|
||||
@@ -503,13 +569,16 @@
|
||||
"noNumbers": "Inga inringningsnummer.",
|
||||
"noPassword": "Inget lösenord",
|
||||
"noRoom": "Inget rum specificerades för inringning.",
|
||||
"noWhiteboard": "Kan inte ladda whiteboard.",
|
||||
"numbers": "Inringningsnummer",
|
||||
"password": "$t(lockRoomPasswordUppercase):",
|
||||
"reachedLimit": "Du har nått gränsen för din prenumeration.",
|
||||
"sip": "SIP adress",
|
||||
"sipAudioOnly": "SIP endast ljud address",
|
||||
"title": "Dela",
|
||||
"tooltip": "Dela länk och information om inringning för mötet",
|
||||
"upgradeOptions": "Vänligen kontrollera om uppgraderingsalternativen är på"
|
||||
"upgradeOptions": "Vänligen kontrollera om uppgraderingsalternativen är på",
|
||||
"whiteboardError": "Problem att ladda whiteboard, var god försök senare."
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Vi slirade lite.",
|
||||
@@ -542,8 +611,7 @@
|
||||
"toggleParticipantsPane": "Visa eller dölj deltagarfönstret",
|
||||
"toggleScreensharing": "Växla mellan kamera och skärmdelning",
|
||||
"toggleShortcuts": "Visa eller dölj kortkommandon",
|
||||
"videoMute": "Aktivera / inaktivera din kamera",
|
||||
"whiteboard": "Visa / dölj whiteboardtavlan"
|
||||
"videoMute": "Aktivera / inaktivera din kamera"
|
||||
},
|
||||
"largeVideo": {
|
||||
"screenIsShared": "Du delar din skärm",
|
||||
@@ -574,6 +642,7 @@
|
||||
"on": "Strömma",
|
||||
"onBy": "{{name}} startade direktströmningen",
|
||||
"pending": "Börja strömma…",
|
||||
"policyError": "",
|
||||
"serviceName": "Livesändningstjänst",
|
||||
"sessionAlreadyActive": "Den här sessionen spelas redan in eller livestreamas.",
|
||||
"signIn": "Logga in med Google",
|
||||
@@ -612,13 +681,13 @@
|
||||
"knockingParticipantList": "Väntande deltagare",
|
||||
"lobbyChatStartedNotification": "{{moderator}} startade en lobbychatt med {{attendee}}",
|
||||
"lobbyChatStartedTitle": "{{moderator}} har startat en lobbychatt med dig.",
|
||||
"lobbyClosed": "Lobbyn har stängts.",
|
||||
"nameField": "Ange ditt namn",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} har nekats att delta av {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} har godkänts att delta av {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Väntrumsläge har inaktiverats av {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Väntrumsläge har aktiverats av {{originParticipantName}}",
|
||||
"notificationTitle": "Väntrum",
|
||||
"passwordField": "Ange möteslösenord",
|
||||
"passwordJoinButton": "Anslut",
|
||||
"title": "Lobby",
|
||||
"toggleLabel": "Aktivera väntrum"
|
||||
@@ -651,6 +720,8 @@
|
||||
"sessionToken": "Sessionstoken",
|
||||
"start": "Starta inspelning",
|
||||
"stop": "Avsluta inspelning",
|
||||
"stopping": "Avslutar inspelning",
|
||||
"wait": "Var god vänta medans vi sparar din inspelning",
|
||||
"yes": "Ja"
|
||||
},
|
||||
"lockRoomPassword": "lösenord",
|
||||
@@ -670,9 +741,13 @@
|
||||
"connectedOneMember": "{{name}} har gått med i mötet",
|
||||
"connectedThreePlusMembers": "{{name}} och {{count}} andra har gått med i mötet",
|
||||
"connectedTwoMembers": "{{first}} och {{second}} har gått med i mötet",
|
||||
"connectionFailed": "Anslutning misslyckades. Var god försök senare!",
|
||||
"dataChannelClosed": "Försämrad videokvalitet",
|
||||
"dataChannelClosedDescription": "Bryggkanalen har kopplats bort och därmed är videokvaliteten begränsad till sin lägsta inställning",
|
||||
"dataChannelClosedDescriptionWithAudio": "Bryggkanalen har kopplats bort och därmed kan abvrott i ljud och bild uppstå.",
|
||||
"dataChannelClosedWithAudio": "Ljud och bild kvalite kan vara försämrad.",
|
||||
"disabledIframe": "Inbäddning är endast avsedd för demonstrationsändamål, så det här samtalet kommer att kopplas ner om {{timeout}} minuter.",
|
||||
"disabledIframeSecondary": "Bädda in {{domain}} är bara till för demo, så detta samtal kommer att kopplas bort inom {{timeout}} minuter. Var god använd <a href='{{jaasDomain}}' rel='nooper noreferrer' target='_blank'>Jitsi som tjänst</a> för att bädda in i produktion.",
|
||||
"disconnected": "frånkopplad",
|
||||
"displayNotifications": "Visa aviseringar för",
|
||||
"dontRemindMe": "Påminn mig inte",
|
||||
@@ -681,6 +756,9 @@
|
||||
"gifsMenu": "GIPHY",
|
||||
"groupTitle": "Notifieringar",
|
||||
"hostAskedUnmute": "Värden vill att du ska stänga av ljudet",
|
||||
"invalidTenant": "Ogiltig tenant",
|
||||
"invalidTenantHyphenDescription": "Tenant du använder har ogiltiga tecken (startar eller slutar med '-').",
|
||||
"invalidTenantLengthDescription": "Tenant du använder är för lång",
|
||||
"invitedOneMember": "{{name}} har bjudits in",
|
||||
"invitedThreePlusMembers": "{{name}} och {{count}} andra har bjudits in",
|
||||
"invitedTwoMembers": "{{first}} och {{second}} har bjudits in",
|
||||
@@ -717,9 +795,9 @@
|
||||
"newDeviceAction": "Använd",
|
||||
"newDeviceAudioTitle": "Ny ljudenhet hittad",
|
||||
"newDeviceCameraTitle": "Ny kamera hittad",
|
||||
"nextToSpeak": "Du är näst i kö för att prata",
|
||||
"noiseSuppressionDesktopAudioDescription": "Brusreducering kan inte aktiveras när du delar skrivbordsljud, vänligen inaktivera det och försök igen.",
|
||||
"noiseSuppressionFailedTitle": "Det gick inte att starta brusreducering",
|
||||
"noiseSuppressionNoTrackDescription": "Slå på mikrofonen först.",
|
||||
"noiseSuppressionStereoDescription": "Brusreducering i stereoljud stöds för närvarande inte.",
|
||||
"oldElectronClientDescription1": "Den version av Jitsi meet som används är gammal och har säkerhetsluckor. Var god uppdatera till den senaste versionen.",
|
||||
"oldElectronClientDescription2": "senast build",
|
||||
@@ -743,13 +821,22 @@
|
||||
"startSilentTitle": "Du gick med utan ljud aktiverat!",
|
||||
"suboptimalBrowserWarning": "Din mötesupplevelse kommer tyvärr inte att bli så bra. Vi letar efter sätt att förbättra detta, men fram till dess kan du försöka använda en av de <a href='{{recommendedBrowserPageLink}}' target='_blank'> fullt stödda webbläsarna </a>.",
|
||||
"suboptimalExperienceTitle": "Webbläsarvarning",
|
||||
"suggestRecordingAction": "Starta",
|
||||
"suggestRecordingDescription": "Vill du starta en inspelning?",
|
||||
"suggestRecordingTitle": "Spela in detta mötet",
|
||||
"unmute": "Slå på mikrofonen",
|
||||
"videoMutedRemotelyDescription": "Du kan alltid slå på den igen.",
|
||||
"videoMutedRemotelyTitle": "Din kamera har inaktiverats av {{participantDisplayName}}!",
|
||||
"videoUnmuteBlockedDescription": "Aktivering av kameran och delning av skrivbord har tillfälligt blockerats på grund av systembegränsningar.",
|
||||
"videoUnmuteBlockedTitle": "Aktivering av kameran och skrivbordsdelning blockerad!",
|
||||
"viewLobby": "Visa lobby",
|
||||
"waitingParticipants": "{{waitingParticipants}} personer"
|
||||
"viewParticipants": "Visa deltagare",
|
||||
"viewVisitors": "Visa besökare",
|
||||
"waitingParticipants": "{{waitingParticipants}} personer",
|
||||
"waitingVisitors": "Väntande besökare i kö: {{waitingVisitors}}",
|
||||
"waitingVisitorsTitle": "Mötet är inte live än!",
|
||||
"whiteboardLimitDescription": "Var god spara, max antal användare har snart uppnåtts och whiteboard kommer att stängas av.",
|
||||
"whiteboardLimitTitle": "Användande whiteboard"
|
||||
},
|
||||
"participantsPane": {
|
||||
"actions": {
|
||||
@@ -760,7 +847,11 @@
|
||||
"askUnmute": "Be om att aktivera ljud",
|
||||
"audioModeration": "Slå på ljudet för sig själva",
|
||||
"blockEveryoneMicCamera": "Inaktivera allas mikrofon och kamera",
|
||||
"breakoutRooms": "Grupprum",
|
||||
"goLive": "Gå live",
|
||||
"invite": "Bjud in någon",
|
||||
"lowerAllHands": "Ta ner allas händer",
|
||||
"lowerHand": "Ta ner handen",
|
||||
"moreModerationActions": "Fler modereringsalternativ",
|
||||
"moreModerationControls": "Fler modereringskontroller",
|
||||
"moreParticipantOptions": "Fler deltagaralternativ",
|
||||
@@ -777,6 +868,8 @@
|
||||
"headings": {
|
||||
"lobby": "Väntrum ({{count}})",
|
||||
"participantsList": "Mötesdeltagare ({{count}})",
|
||||
"visitorInQueue": "(väntande {{count}})",
|
||||
"visitorRequests": "(förfrågningar {{count}})",
|
||||
"visitors": "Gäster ({{count}})",
|
||||
"waitingLobby": "Väntar i väntrum ({{count}})"
|
||||
},
|
||||
@@ -789,10 +882,13 @@
|
||||
"pinnedParticipant": "Deltagaren är fäst",
|
||||
"polls": {
|
||||
"answer": {
|
||||
"edit": "Redigera",
|
||||
"send": "Skicka",
|
||||
"skip": "Avstå",
|
||||
"submit": "Skicka"
|
||||
},
|
||||
"by": "Av {{ name }}",
|
||||
"closeButton": "Stäng omröstning",
|
||||
"create": {
|
||||
"addOption": "Lägg till alternativ",
|
||||
"answerPlaceholder": "Alternativ",
|
||||
@@ -802,6 +898,7 @@
|
||||
"pollQuestion": "Fråga för omröstning",
|
||||
"questionPlaceholder": "Ställ en fråga",
|
||||
"removeOption": "Ta bort alternativ",
|
||||
"save": "Spara",
|
||||
"send": "Skicka"
|
||||
},
|
||||
"errors": {
|
||||
@@ -831,9 +928,11 @@
|
||||
"configuringDevices": "Konfigurerar enheter…",
|
||||
"connectedWithAudioQ": "Är din mikrofon ansluten?",
|
||||
"connection": {
|
||||
"failed": "Anslutningstest misslyckades!",
|
||||
"good": "Din internetanslutning fungerar bra!",
|
||||
"nonOptimal": "Din internetanslutning är inte optimal",
|
||||
"poor": "Din internetanslutning fungerar dåligt"
|
||||
"poor": "Din internetanslutning fungerar dåligt",
|
||||
"running": "Kör anslutningstest..."
|
||||
},
|
||||
"connectionDetails": {
|
||||
"audioClipping": "Ditt ljud kan upplevas hackigt.",
|
||||
@@ -842,6 +941,7 @@
|
||||
"goodQuality": "Grymt bra! Din mediekvalitet kommer att bli bra.",
|
||||
"noMediaConnectivity": "Vi kunde inte hitta ett sätt att upprätta mediaanslutning för detta test. Detta orsakas vanligtvis av en brandvägg eller NAT.",
|
||||
"noVideo": "Vi förväntar oss att din video kommer ha låg kvalitet eller inte fungera.",
|
||||
"testFailed": "Anslutningstestet stötte på oväntade problem, men det behöver inte påverka din upplevelse.",
|
||||
"undetectable": "Om du fortfarande inte kan ringa i webbläsaren rekommenderar vi att du ser till att dina högtalare, mikrofon och kamera är korrekt inställda, att du har beviljat din webbläsare rättigheter att använda din mikrofon och kamera och att din webbläsarversion är uppdaterad.",
|
||||
"veryPoorConnection": "Vi förväntar oss att din samtalskvalitet är riktigt dålig.",
|
||||
"videoFreezing": "Vi förväntar oss att din video fryser, blir svart och pixeleras.",
|
||||
@@ -874,6 +974,7 @@
|
||||
"or": "eller",
|
||||
"premeeting": "Förmöte",
|
||||
"proceedAnyway": "Fortsätt ändå",
|
||||
"recordingWarning": "",
|
||||
"screenSharingError": "Skärmdelningsfel:",
|
||||
"showScreen": "Aktivera skärmen före mötet",
|
||||
"startWithPhone": "Börja med telefonljud",
|
||||
@@ -940,7 +1041,6 @@
|
||||
"limitNotificationDescriptionNative": "På grund av stor efterfrågan är din inspelning begränsad till {{limit}} min. För obegränsade inspelningar, försök <3>{{app}}</3>.",
|
||||
"limitNotificationDescriptionWeb": "På grund av stor efterfrågan är din inspelning begränsad till {{limit}} min. För obegränsade inspelningar, prova <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
|
||||
"linkGenerated": "Vi har genererat en länk till din inspelning.",
|
||||
"live": "LIVE",
|
||||
"localRecordingNoNotificationWarning": "Inspelningen kommer inte att meddelas till andra deltagare. Du måste meddela dem att mötet inspelas.",
|
||||
"localRecordingNoVideo": "Video spelas inte in",
|
||||
"localRecordingStartWarning": "Se till att du stoppar inspelningen innan du avslutar mötet för att spara det.",
|
||||
@@ -957,13 +1057,16 @@
|
||||
"onBy": "{{name}} påbörjade inspelningen",
|
||||
"onlyRecordSelf": "Spela bara in mina ljud- och videoströmmar",
|
||||
"pending": "Förbereder inspelning av mötet…",
|
||||
"rec": "REC",
|
||||
"policyError": "Du försökte starta inspelningen för fort. Var god försök senare!",
|
||||
"recordAudioAndVideo": "Spela in ljud och bild",
|
||||
"recordTranscription": "Spela in transkribiering",
|
||||
"saveLocalRecording": "Spara inspelningsfil lokalt (beta)",
|
||||
"serviceDescription": "Din inspelning kommer att sparas av inspelningstjänsten",
|
||||
"serviceDescriptionCloud": "Molninspelning",
|
||||
"serviceDescriptionCloudInfo": "Inspelade möten rensas automatiskt 24 timmar efter inspelningstiden.",
|
||||
"serviceName": "Inspelningstjänst",
|
||||
"sessionAlreadyActive": "Den här sessionen spelas redan in eller livestreamas.",
|
||||
"showAdvancedOptions": "Visa avancerade inställningar",
|
||||
"signIn": "Logga in",
|
||||
"signOut": "Logga ut",
|
||||
"surfaceError": "Välj aktuell flik.",
|
||||
@@ -1003,6 +1106,7 @@
|
||||
"desktopShareWarning": "Du måste starta om skärmdelningen för att de nya inställningarna ska träda i kraft.",
|
||||
"devices": "Enheter",
|
||||
"followMe": "Alla följer mig",
|
||||
"followMeRecorder": "Den som spelar in följer mig",
|
||||
"framesPerSecond": "bildrutor per sekund",
|
||||
"incomingMessage": "Inkommande meddelande",
|
||||
"language": "Språk",
|
||||
@@ -1040,6 +1144,7 @@
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Varning",
|
||||
"alertURLText": "Den angivna serverlänken är felaktig",
|
||||
"apply": "Tillämpa",
|
||||
"buildInfoSection": "Versionsinformation",
|
||||
"conferenceSection": "Konferens",
|
||||
"disableCallIntegration": "Tillåt inte deltagande via telefon",
|
||||
@@ -1050,12 +1155,14 @@
|
||||
"displayNamePlaceholderText": "Exempel: John Doe",
|
||||
"email": "E-post",
|
||||
"emailPlaceholderText": "mejl@exempel.se",
|
||||
"gavatarMessage": "Om din e-post är associerad med ett Gravatar konto, så kommer vi använda det för att visa din profilbild.",
|
||||
"goTo": "Gå till",
|
||||
"header": "Inställningar",
|
||||
"help": "Hjälp",
|
||||
"links": "Länkar",
|
||||
"privacy": "Sekretess",
|
||||
"profileSection": "Profil",
|
||||
"sdkVersion": "",
|
||||
"serverURL": "Serverlänk",
|
||||
"showAdvanced": "Visa avancerade inställningar",
|
||||
"startCarModeInLowBandwidthMode": "Starta billäget i lågbandbreddsläge",
|
||||
@@ -1076,6 +1183,7 @@
|
||||
"fearful": "Rädd",
|
||||
"happy": "Glad",
|
||||
"hours": "{{count}} h",
|
||||
"labelTooltip": "",
|
||||
"minutes": "{{count}} m",
|
||||
"name": "Namn",
|
||||
"neutral": "Neutral",
|
||||
@@ -1108,7 +1216,7 @@
|
||||
"audioOnly": "Slå av eller på ljudet",
|
||||
"audioRoute": "Välj ljudenhet",
|
||||
"boo": "Bua",
|
||||
"breakoutRoom": "Anslut eller lämna grupprum",
|
||||
"breakoutRooms": "Grupprum",
|
||||
"callQuality": "Hantera videokvalitet",
|
||||
"carmode": "Billäge",
|
||||
"cc": "Slå av eller på undertexter",
|
||||
@@ -1146,6 +1254,7 @@
|
||||
"lobbyButton": "Aktivera / inaktivera lobbyläge",
|
||||
"localRecording": "Växla lokala inspelningskontroller",
|
||||
"lockRoom": "Växla möteslösenord",
|
||||
"love": "Kärlek",
|
||||
"lowerHand": "Sänk din hand",
|
||||
"moreActions": "Fler åtgärder",
|
||||
"moreActionsMenu": "Menyn Fler åtgärder",
|
||||
@@ -1155,6 +1264,7 @@
|
||||
"muteEveryoneElse": "Stäng av ljudet för alla andra",
|
||||
"muteEveryoneElsesVideoStream": "Stoppa alla andras video",
|
||||
"muteEveryonesVideoStream": "Stoppa allas video",
|
||||
"muteGUMPending": "Ansluter din mikrofon",
|
||||
"noiseSuppression": "Brusdämpning",
|
||||
"openChat": "Öppna chatt",
|
||||
"participants": "Öppna deltagarfönstret",
|
||||
@@ -1162,6 +1272,7 @@
|
||||
"privateMessage": "Skicka privat meddelande",
|
||||
"profile": "Redigera din profil",
|
||||
"raiseHand": "Räck upp handen",
|
||||
"react": "Reaktioner på meddelanden",
|
||||
"reactions": "Reaktioner",
|
||||
"reactionsMenu": "Reaktionsmeny",
|
||||
"recording": "Växla inspelning",
|
||||
@@ -1188,6 +1299,7 @@
|
||||
"unmute": "Slå på ljudet",
|
||||
"videoblur": "Växla videooskärpa",
|
||||
"videomute": "Stoppa kamera",
|
||||
"videomuteGUMPending": "Ansluter din kamera",
|
||||
"videounmute": "Starta kameran"
|
||||
},
|
||||
"addPeople": "Lägg till personer i samtal",
|
||||
@@ -1232,12 +1344,14 @@
|
||||
"lobbyButtonEnable": "Aktivera väntrumsläge",
|
||||
"login": "Logga in",
|
||||
"logout": "Logga ut",
|
||||
"love": "Kärlek",
|
||||
"lowerYourHand": "Ta ner handen",
|
||||
"moreActions": "Fler handlingar",
|
||||
"moreOptions": "Visa fler alternativ",
|
||||
"mute": "Slå av/på ljud",
|
||||
"muteEveryone": "Tysta alla",
|
||||
"muteEveryonesVideo": "Inaktivera allas kamera",
|
||||
"muteGUMPending": "Ansluter din mikrofon",
|
||||
"noAudioSignalDesc": "Om du inte avsiktligt stängde av det från systeminställningar eller hårdvara, överväg att byta enhet.",
|
||||
"noAudioSignalDescSuggestion": "Om du inte avsiktligt tystade det från systeminställningar eller hårdvara, överväg att byta till den föreslagna enheten.",
|
||||
"noAudioSignalDialInDesc": "Du kan också ringa in med:",
|
||||
@@ -1254,8 +1368,9 @@
|
||||
"profile": "Redigera din profil",
|
||||
"raiseHand": "Räck upp / ta ner din hand",
|
||||
"raiseYourHand": "Räck upp handen",
|
||||
"reactionBoo": "Bua",
|
||||
"reactionClap": "Klappa",
|
||||
"reactionBoo": "Skicka Bu",
|
||||
"reactionClap": "Klappa i händerna",
|
||||
"reactionHeart": "Skicka kärlek",
|
||||
"reactionLaugh": "Skratta",
|
||||
"reactionLike": "Skicka tummen upp",
|
||||
"reactionSilence": "Skicka tyst reaktion",
|
||||
@@ -1283,16 +1398,14 @@
|
||||
"unmute": "Slå på ljud",
|
||||
"videoSettings": "Videoinställningar",
|
||||
"videomute": "Inaktivera kameran",
|
||||
"videomuteGUMPending": "Ansluter din kamera",
|
||||
"videounmute": "Aktivera kameran"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Aktivera / Inaktivera undertexter",
|
||||
"error": "Transkriberingen misslyckades. Försök igen.",
|
||||
"expandedLabel": "Transkribering är aktiverad",
|
||||
"failedToStart": "Det gick inte att starta transkribering",
|
||||
"failed": "Transkribiering misslyckades",
|
||||
"labelToolTip": "Mötet transkriberas",
|
||||
"off": "Transkribering avslutades",
|
||||
"pending": "Förbereder transkribering av mötet…",
|
||||
"sourceLanguageDesc": "För närvarande är mötesspråket inställt på <b>{{sourceLanguage}}</b>. <br/> Du kan ändra det från ",
|
||||
"sourceLanguageHere": "här",
|
||||
"start": "Börja visa undertexter",
|
||||
@@ -1339,6 +1452,7 @@
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Anslutningsinformation",
|
||||
"demote": "Gör till besökare",
|
||||
"domute": "Tysta",
|
||||
"domuteOthers": "Inaktivera ljud för alla andra",
|
||||
"domuteVideo": "Inaktivera kamera",
|
||||
@@ -1362,6 +1476,10 @@
|
||||
"videomute": "Deltagaren har stängt av kameran"
|
||||
},
|
||||
"virtualBackground": {
|
||||
"accessibilityLabel": {
|
||||
"currentBackground": "Nuvarande bakgrund: {{background}}",
|
||||
"selectBackground": "Välj bakgrund"
|
||||
},
|
||||
"addBackground": "Lägg till bakgrund",
|
||||
"apply": "Tillämpa",
|
||||
"backgroundEffectError": "Det gick inte att tillämpa bakgrundseffekt.",
|
||||
@@ -1387,11 +1505,22 @@
|
||||
},
|
||||
"visitors": {
|
||||
"chatIndicator": "(besökare)",
|
||||
"joinMeeting": {
|
||||
"description": "Du är just nu en beskökare i konferensen",
|
||||
"raiseHand": "Räck upp handen",
|
||||
"title": "Gå med i mötet",
|
||||
"wishToSpeak": "Om du vill prata, räck upp handen och vänta på att en moderator ska godkänna."
|
||||
},
|
||||
"labelTooltip": "Antal besökare: {{count}}",
|
||||
"notification": {
|
||||
"description": "Räck upp handen för att delta",
|
||||
"demoteDescription": "Skickad hit av {{actor}}, räck upphanden för att delta",
|
||||
"noMainParticipantsDescription": "En deltagare behöver starta mötet. Var god försök igen senare.",
|
||||
"noMainParticipantsTitle": "Det är mötet har inte startat än",
|
||||
"noVisitorLobby": "Du kan inte gå med medans lobby är påslagen för mötet.",
|
||||
"notAllowedPromotion": "En deltagare behöver godkänna din förfrågan först.",
|
||||
"title": "Du är en besökare i mötet"
|
||||
}
|
||||
},
|
||||
"waitingMessage": "Du kommer att komma in i mötet när det är live!"
|
||||
},
|
||||
"volumeSlider": "Volymreglage",
|
||||
"welcomepage": {
|
||||
@@ -1449,6 +1578,7 @@
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Whiteboard"
|
||||
}
|
||||
},
|
||||
"screenTitle": "Whiteboard"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,6 +851,8 @@ function initCommands() {
|
||||
'overwrite-config': config => {
|
||||
const whitelistedConfig = getWhitelistedJSON('config', config);
|
||||
|
||||
logger.info(`Overwriting config with: ${JSON.stringify(whitelistedConfig)}`);
|
||||
|
||||
APP.store.dispatch(overwriteConfig(whitelistedConfig));
|
||||
},
|
||||
'toggle-virtual-background': () => {
|
||||
|
||||
71
package-lock.json
generated
71
package-lock.json
generated
@@ -62,7 +62,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1901.0.0+22daa357/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -13324,10 +13324,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -13348,7 +13349,7 @@
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.10",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
@@ -13363,6 +13364,10 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/array-flatten": {
|
||||
@@ -16954,8 +16959,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1901.0.0+22daa357/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-GWx9NSfUYoxfM9J+Le0w9Nu2+K0nAITqT9puNwqrSXwncE5PKf1uS/DZlMSK4yBKJ+EDvP+YBhDOa6wtxcXODw==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -18788,15 +18793,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -19772,10 +19778,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
||||
"dev": true
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
@@ -24383,9 +24390,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz",
|
||||
"integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==",
|
||||
"version": "6.21.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
||||
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -35147,9 +35154,9 @@
|
||||
}
|
||||
},
|
||||
"express": {
|
||||
"version": "4.21.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"accepts": "~1.3.8",
|
||||
@@ -35171,7 +35178,7 @@
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.10",
|
||||
"path-to-regexp": "0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
@@ -37739,8 +37746,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1901.0.0+22daa357/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-GWx9NSfUYoxfM9J+Le0w9Nu2+K0nAITqT9puNwqrSXwncE5PKf1uS/DZlMSK4yBKJ+EDvP+YBhDOa6wtxcXODw==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -39114,9 +39121,9 @@
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
@@ -39781,9 +39788,9 @@
|
||||
}
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"path-type": {
|
||||
@@ -42980,9 +42987,9 @@
|
||||
}
|
||||
},
|
||||
"undici": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz",
|
||||
"integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==",
|
||||
"version": "6.21.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
|
||||
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
|
||||
"dev": true
|
||||
},
|
||||
"undici-types": {
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1901.0.0+22daa357/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
||||
@@ -86,12 +86,16 @@ export function createDesiredLocalTracks(...desiredTypes: any) {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
|
||||
if (desiredTypes.length === 0) {
|
||||
const { startSilent } = state['features/base/config'];
|
||||
const { video } = state['features/base/media'];
|
||||
|
||||
// XXX: Always create the audio track early, even if it will be muted.
|
||||
// This fixes a timing issue when adding the track to the conference which
|
||||
// manifests primarily on iOS 15.
|
||||
desiredTypes.push(MEDIA_TYPE.AUDIO);
|
||||
if (!startSilent) {
|
||||
// Always create the audio track early, even if it will be muted.
|
||||
// This fixes a timing issue when adding the track to the conference which
|
||||
// manifests primarily on iOS 15.
|
||||
// Unless we are silent, of course.
|
||||
desiredTypes.push(MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
|
||||
// XXX When the app is coming into the foreground from the
|
||||
// background in order to handle a URL, it may realize the new
|
||||
|
||||
@@ -118,8 +118,8 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
* @param {IProps} props - The props object.
|
||||
* @returns {boolean} - True if the prejoin screen should be displayed and false otherwise.
|
||||
*/
|
||||
function shouldShowPrejoin({ _showPrejoin, _showVisitorsQueue }: IProps) {
|
||||
return _showPrejoin && !_showVisitorsQueue;
|
||||
function shouldShowPrejoin({ _showLobby, _showPrejoin, _showVisitorsQueue }: IProps) {
|
||||
return _showPrejoin && !_showVisitorsQueue && !_showLobby;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,12 +58,14 @@ ReducerRegistry.register<ILobbyState>('features/lobby', (state = DEFAULT_STATE,
|
||||
...state,
|
||||
isDisplayNameRequiredError: true
|
||||
};
|
||||
} else if (action.error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
|
||||
return {
|
||||
...state,
|
||||
knocking: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
knocking: false
|
||||
};
|
||||
return state;
|
||||
}
|
||||
case CONFERENCE_JOINED:
|
||||
case CONFERENCE_LEFT:
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
CONFERENCE_LEFT
|
||||
} from '../../base/conference/actionTypes';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { SET_CONFIG } from '../../base/config/actionTypes';
|
||||
import { AUDIO_FOCUS_DISABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import { parseURIString } from '../../base/util/uri';
|
||||
|
||||
import { _SET_AUDIOMODE_DEVICES, _SET_AUDIOMODE_SUBSCRIPTIONS } from './actionTypes';
|
||||
import logger from './logger';
|
||||
@@ -44,7 +46,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
case APP_WILL_MOUNT:
|
||||
_appWillMount(store);
|
||||
case CONFERENCE_FAILED: // eslint-disable-line no-fallthrough
|
||||
case CONFERENCE_FAILED:
|
||||
case CONFERENCE_LEFT:
|
||||
|
||||
/*
|
||||
@@ -60,6 +62,23 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SET_AUDIO_ONLY:
|
||||
return _updateAudioMode(store, next, action);
|
||||
|
||||
case SET_CONFIG: {
|
||||
const { locationURL } = store.getState()['features/base/connection'];
|
||||
const location = parseURIString(locationURL?.href ?? '');
|
||||
|
||||
/**
|
||||
* Don't touch the current value if there is no room in the URL. This
|
||||
* avoids audio cutting off for a moment right after the user leaves
|
||||
* a meeting. The next meeting join will set it to the right value.
|
||||
*/
|
||||
if (location.room) {
|
||||
const { startSilent } = action.config;
|
||||
|
||||
AudioMode.setDisabled?.(Boolean(startSilent));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable no-fallthrough */
|
||||
|
||||
@@ -130,7 +130,8 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], get
|
||||
const tmp = {
|
||||
id: pollId,
|
||||
answers,
|
||||
question
|
||||
question,
|
||||
senderId
|
||||
};
|
||||
|
||||
// Check integrity of the poll data.
|
||||
@@ -151,7 +152,7 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], get
|
||||
name: answer,
|
||||
voters: []
|
||||
};
|
||||
}).slice(MAX_ANSWERS),
|
||||
}).slice(0, MAX_ANSWERS),
|
||||
saved: false,
|
||||
editing: false
|
||||
};
|
||||
@@ -172,7 +173,7 @@ function _handleReceivePollsMessage(data: any, dispatch: IStore['dispatch'], get
|
||||
const receivedAnswer: IAnswer = {
|
||||
voterId,
|
||||
pollId,
|
||||
answers: answers.slice(MAX_ANSWERS)
|
||||
answers: answers.slice(0, MAX_ANSWERS)
|
||||
};
|
||||
|
||||
dispatch(receiveAnswer(pollId, receivedAnswer));
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Image } from 'react-native';
|
||||
import { Image, View, ViewStyle } from 'react-native';
|
||||
import { SvgCssUri } from 'react-native-svg';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
@@ -13,6 +14,7 @@ interface IProps extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: any;
|
||||
id?: string;
|
||||
isToolboxButton?: boolean;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
@@ -40,20 +42,30 @@ class CustomOptionButton extends AbstractButton<IProps> {
|
||||
}
|
||||
|
||||
if (this.iconSrc?.includes('svg')) {
|
||||
iconComponent
|
||||
= (
|
||||
<SvgCssUri
|
||||
style = { styles.iconImageStyles }
|
||||
uri = { this.iconSrc } />);
|
||||
iconComponent = (
|
||||
<SvgCssUri
|
||||
height = { BaseTheme.spacing[4] }
|
||||
uri = { this.iconSrc }
|
||||
width = { BaseTheme.spacing[4] } />
|
||||
);
|
||||
} else {
|
||||
iconComponent
|
||||
= (
|
||||
<Image
|
||||
source = {{ uri: this.iconSrc }}
|
||||
style = { styles.iconImageStyles } />);
|
||||
iconComponent = (
|
||||
<Image
|
||||
height = { BaseTheme.spacing[4] }
|
||||
resizeMode = { 'contain' }
|
||||
source = {{ uri: this.iconSrc }}
|
||||
width = { BaseTheme.spacing[4] } />
|
||||
);
|
||||
}
|
||||
|
||||
return iconComponent;
|
||||
return (
|
||||
<View
|
||||
style = { this.props.isToolboxButton && [
|
||||
styles.toolboxButtonIconContainer,
|
||||
{ backgroundColor: this.backgroundColor } ] as ViewStyle }>
|
||||
{ iconComponent }
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
label = this.text || '';
|
||||
|
||||
@@ -237,6 +237,7 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
dispatch(customButtonPressed(id, text))
|
||||
}
|
||||
icon = { icon }
|
||||
isToolboxButton = { false }
|
||||
key = { id }
|
||||
text = { text } />
|
||||
))
|
||||
|
||||
@@ -126,6 +126,7 @@ function Toolbox(props: IProps) {
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(id, text)) }
|
||||
icon = { icon }
|
||||
isToolboxButton = { true }
|
||||
key = { id } />
|
||||
))
|
||||
}
|
||||
@@ -145,7 +146,12 @@ function Toolbox(props: IProps) {
|
||||
style = { style as ViewStyle }>
|
||||
{
|
||||
_customToolbarButtons
|
||||
? renderCustomToolboxButtons()
|
||||
? <>
|
||||
{ renderCustomToolboxButtons() }
|
||||
{ !_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } /> }
|
||||
</>
|
||||
: <>
|
||||
{!_iAmVisitor && <AudioMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
|
||||
@@ -101,13 +101,16 @@ const styles = {
|
||||
maxWidth: 580,
|
||||
marginHorizontal: 'auto',
|
||||
marginVertical: BaseTheme.spacing[0],
|
||||
paddingHorizontal: BaseTheme.spacing[1],
|
||||
paddingHorizontal: BaseTheme.spacing[2],
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
iconImageStyles: {
|
||||
height: BaseTheme.spacing[4],
|
||||
width: BaseTheme.spacing[4]
|
||||
toolboxButtonIconContainer: {
|
||||
alignItems: 'center',
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
height: BaseTheme.spacing[7],
|
||||
justifyContent: 'center',
|
||||
width: BaseTheme.spacing[7]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -89,9 +89,14 @@ function UploadImageButton({
|
||||
|
||||
|
||||
const uploadImage = useCallback(async e => {
|
||||
const reader = new FileReader();
|
||||
const imageFile = e.target.files;
|
||||
|
||||
if (imageFile.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const url = await resizeImage(reader.result);
|
||||
|
||||
@@ -211,6 +211,20 @@ local function cancel_destroy_timer(room)
|
||||
end
|
||||
end
|
||||
|
||||
local function destroy_with_conference_ended(room)
|
||||
-- if the room is being destroyed, ignore
|
||||
if room.destroying then
|
||||
return;
|
||||
end
|
||||
|
||||
cancel_destroy_timer(room);
|
||||
|
||||
local main_count, visitors_count = get_occupant_counts(room);
|
||||
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
||||
room:destroy(nil, 'Conference ended.');
|
||||
return true;
|
||||
end
|
||||
|
||||
-- schedules a new destroy timer which will destroy the room if there are no visitors after the timeout
|
||||
local function schedule_destroy_timer(room)
|
||||
cancel_destroy_timer(room);
|
||||
@@ -271,6 +285,10 @@ module:hook('muc-occupant-left', function (event)
|
||||
if visitors_count == 0 then
|
||||
schedule_destroy_timer(room);
|
||||
end
|
||||
|
||||
if main_count == 0 then
|
||||
destroy_with_conference_ended(room);
|
||||
end
|
||||
end);
|
||||
|
||||
-- forward visitor presences to jicofo
|
||||
@@ -407,7 +425,6 @@ local function stanza_handler(event)
|
||||
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found %s in stanza_handler', room_jid);
|
||||
return;
|
||||
end
|
||||
|
||||
@@ -619,12 +636,7 @@ local function iq_from_main_handler(event)
|
||||
respond_iq_result(origin, stanza);
|
||||
|
||||
if process_disconnect then
|
||||
cancel_destroy_timer(room);
|
||||
|
||||
local main_count, visitors_count = get_occupant_counts(room);
|
||||
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
|
||||
room:destroy(nil, 'Conference ended.');
|
||||
return true;
|
||||
return destroy_with_conference_ended(room);
|
||||
end
|
||||
|
||||
-- if there is password supplied use it
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
module:set_global();
|
||||
|
||||
local formdecode = require "util.http".formdecode;
|
||||
local region_header_name = module:get_option_string('region_header_name', 'x_proxy_region');
|
||||
|
||||
-- Extract the following parameters from the URL and set them in the session:
|
||||
-- * previd: for session resumption
|
||||
@@ -24,6 +25,8 @@ function init_session(event)
|
||||
session.jitsi_web_query_room = params.room;
|
||||
session.jitsi_web_query_prefix = params.prefix or "";
|
||||
end
|
||||
|
||||
session.user_region = request.headers[region_header_name];
|
||||
end
|
||||
|
||||
module:hook_global("bosh-session", init_session, 1);
|
||||
|
||||
@@ -75,17 +75,35 @@ module:hook('muc-broadcast-presence', function (event)
|
||||
end
|
||||
end);
|
||||
|
||||
--- Avoids any participant joining the room in the interval between creating the room
|
||||
--- and jicofo entering the room
|
||||
module:hook('muc-occupant-pre-join', function (event)
|
||||
local room, stanza = event.room, event.stanza;
|
||||
|
||||
-- we skip processing only if jicofo_lock is set to false
|
||||
if room._data.jicofo_lock == false or is_healthcheck_room(room.jid) then
|
||||
local function process_region(session, stanza)
|
||||
if not session.user_region then
|
||||
return;
|
||||
end
|
||||
|
||||
local region = stanza:get_child_text('jitsi_participant_region');
|
||||
if region then
|
||||
return;
|
||||
end
|
||||
|
||||
stanza:tag('jitsi_participant_region'):text(session.user_region):up();
|
||||
end
|
||||
|
||||
--- Avoids any participant joining the room in the interval between creating the room
|
||||
--- and jicofo entering the room
|
||||
module:hook('muc-occupant-pre-join', function (event)
|
||||
local occupant, room, stanza = event.occupant, event.room, event.stanza;
|
||||
|
||||
local is_health_room = is_healthcheck_room(room.jid);
|
||||
-- check for region
|
||||
if not is_admin(occupant.bare_jid) and not is_health_room then
|
||||
process_region(event.origin, stanza);
|
||||
end
|
||||
|
||||
-- we skip processing only if jicofo_lock is set to false
|
||||
if room._data.jicofo_lock == false or is_health_room then
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant = event.occupant;
|
||||
if ends_with(occupant.nick, '/focus') then
|
||||
module:fire_event('jicofo-unlock-room', { room = room; });
|
||||
else
|
||||
|
||||
@@ -474,12 +474,20 @@ end
|
||||
|
||||
-- Returns the initiator extension if the stanza is coming from a sip jigasi
|
||||
function is_sip_jigasi(stanza)
|
||||
if not stanza then
|
||||
return false;
|
||||
end
|
||||
|
||||
return stanza:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
|
||||
end
|
||||
|
||||
function is_transcriber_jigasi(stanza)
|
||||
if not stanza then
|
||||
return false;
|
||||
end
|
||||
|
||||
local features = stanza:get_child('features');
|
||||
if not features then
|
||||
if not features then
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -532,7 +540,7 @@ function is_sip_jibri_join(stanza)
|
||||
end
|
||||
|
||||
function is_jibri(occupant)
|
||||
return starts_with_one_of(occupant.jid, RECORDER_PREFIXES)
|
||||
return starts_with_one_of(type(occupant) == "string" and occupant or occupant.jid, RECORDER_PREFIXES)
|
||||
end
|
||||
|
||||
-- process a host module directly if loaded or hooks to wait for its load
|
||||
|
||||
@@ -10,8 +10,11 @@ import ChatPanel from '../pageobjects/ChatPanel';
|
||||
import Filmstrip from '../pageobjects/Filmstrip';
|
||||
import IframeAPI from '../pageobjects/IframeAPI';
|
||||
import InviteDialog from '../pageobjects/InviteDialog';
|
||||
import LobbyScreen from '../pageobjects/LobbyScreen';
|
||||
import Notifications from '../pageobjects/Notifications';
|
||||
import ParticipantsPane from '../pageobjects/ParticipantsPane';
|
||||
import PreJoinScreen from '../pageobjects/PreJoinScreen';
|
||||
import SecurityDialog from '../pageobjects/SecurityDialog';
|
||||
import SettingsDialog from '../pageobjects/SettingsDialog';
|
||||
import Toolbar from '../pageobjects/Toolbar';
|
||||
import VideoQualityDialog from '../pageobjects/VideoQualityDialog';
|
||||
@@ -19,6 +22,11 @@ import VideoQualityDialog from '../pageobjects/VideoQualityDialog';
|
||||
import { LOG_PREFIX, logInfo } from './browserLogger';
|
||||
import { IContext, IJoinOptions } from './types';
|
||||
|
||||
export const P1_DISPLAY_NAME = 'p1';
|
||||
export const P2_DISPLAY_NAME = 'p2';
|
||||
export const P3_DISPLAY_NAME = 'p3';
|
||||
export const P4_DISPLAY_NAME = 'p4';
|
||||
|
||||
/**
|
||||
* Participant.
|
||||
*/
|
||||
@@ -29,6 +37,7 @@ export class Participant {
|
||||
* @private
|
||||
*/
|
||||
private _name: string;
|
||||
private _displayName: string;
|
||||
private _endpointId: string;
|
||||
private _jwt?: string;
|
||||
|
||||
@@ -103,6 +112,13 @@ export class Participant {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*/
|
||||
get displayName() {
|
||||
return this._displayName || this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log to the participants log file.
|
||||
*
|
||||
@@ -135,7 +151,7 @@ export class Participant {
|
||||
if (!options.skipDisplayName) {
|
||||
// @ts-ignore
|
||||
config.userInfo = {
|
||||
displayName: options.displayName || this._name
|
||||
displayName: this._displayName = options.displayName || this._name
|
||||
};
|
||||
}
|
||||
|
||||
@@ -173,7 +189,9 @@ export class Participant {
|
||||
await this.driver.switchFrame(mainFrame);
|
||||
}
|
||||
|
||||
await this.waitToJoinMUC();
|
||||
if (!options.skipWaitToJoin) {
|
||||
await this.waitToJoinMUC();
|
||||
}
|
||||
|
||||
await this.postLoadProcess(options.skipInMeetingChecks);
|
||||
}
|
||||
@@ -234,7 +252,7 @@ export class Participant {
|
||||
*/
|
||||
async waitForPageToLoad(): Promise<void> {
|
||||
return this.driver.waitUntil(
|
||||
async () => await this.driver.execute(() => document.readyState === 'complete'),
|
||||
() => this.driver.execute(() => document.readyState === 'complete'),
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: `Timeout waiting for Page Load Request to complete for ${this.name}.`
|
||||
@@ -307,8 +325,8 @@ export class Participant {
|
||||
async waitForIceConnected(): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
await driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
||||
return driver.waitUntil(() =>
|
||||
driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected ICE to be connected for 15s for ${this.name}`
|
||||
});
|
||||
@@ -323,17 +341,39 @@ export class Participant {
|
||||
timeout = 15_000, msg = `expected to receive/send data in 15s for ${this.name}`): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
await driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
uploadBitrate: bitrateMap.upload || 0,
|
||||
downloadBitrate: bitrateMap.download || 0
|
||||
};
|
||||
return driver.waitUntil(() => driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
uploadBitrate: bitrateMap.upload || 0,
|
||||
downloadBitrate: bitrateMap.download || 0
|
||||
};
|
||||
|
||||
return rtpStats.uploadBitrate > 0 && rtpStats.downloadBitrate > 0;
|
||||
}), {
|
||||
return rtpStats.uploadBitrate > 0 && rtpStats.downloadBitrate > 0;
|
||||
}), {
|
||||
timeout,
|
||||
timeoutMsg: msg
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for send and receive data.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async waitForSendData(
|
||||
timeout = 15_000, msg = `expected to send data in 15s for ${this.name}`): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(() => driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
uploadBitrate: bitrateMap.upload || 0
|
||||
};
|
||||
|
||||
return rtpStats.uploadBitrate > 0;
|
||||
}), {
|
||||
timeout,
|
||||
timeoutMsg: msg
|
||||
});
|
||||
@@ -348,10 +388,10 @@ export class Participant {
|
||||
waitForRemoteStreams(number: number): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
await driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
||||
return driver.waitUntil(() =>
|
||||
driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected remote streams in 15s for ${this.name}`
|
||||
timeoutMsg: `expected number of remote streams:${number} in 15s for ${this.name}`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,8 +405,7 @@ export class Participant {
|
||||
waitForParticipants(number: number, msg?: string): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(async () =>
|
||||
await driver.execute(count => APP.conference.listMembers().length === count, number), {
|
||||
return driver.waitUntil(() => driver.execute(count => APP.conference.listMembers().length === count, number), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
|
||||
});
|
||||
@@ -440,6 +479,15 @@ export class Participant {
|
||||
return new VideoQualityDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the security Dialog.
|
||||
*
|
||||
* @returns {SecurityDialog}
|
||||
*/
|
||||
getSecurityDialog(): SecurityDialog {
|
||||
return new SecurityDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings Dialog.
|
||||
*
|
||||
@@ -449,6 +497,20 @@ export class Participant {
|
||||
return new SettingsDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prejoin screen.
|
||||
*/
|
||||
getPreJoinScreen(): PreJoinScreen {
|
||||
return new PreJoinScreen(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lobby screen.
|
||||
*/
|
||||
getLobbyScreen(): LobbyScreen {
|
||||
return new LobbyScreen(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches to the iframe API context
|
||||
*/
|
||||
@@ -476,6 +538,26 @@ export class Participant {
|
||||
* Hangups the participant by leaving the page. base.html is an empty page on all deployments.
|
||||
*/
|
||||
async hangup() {
|
||||
const current = await this.driver.getUrl();
|
||||
|
||||
// already hangup
|
||||
if (current.endsWith('/base.html')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do a hangup, to make sure unavailable presence is sent
|
||||
await this.driver.execute(() => typeof APP !== 'undefined' && APP?.conference?.hangup());
|
||||
|
||||
// let's give it some time to leave the muc, we redirect after hangup so we should wait for the
|
||||
// change of url
|
||||
await this.driver.waitUntil(
|
||||
async () => current !== await this.driver.getUrl(),
|
||||
{
|
||||
timeout: 5000,
|
||||
timeoutMsg: `${this.name} did not leave the muc in 5s`
|
||||
}
|
||||
);
|
||||
|
||||
await this.driver.url('/base.html');
|
||||
}
|
||||
|
||||
@@ -495,7 +577,7 @@ export class Participant {
|
||||
* Returns the local display name.
|
||||
*/
|
||||
async getLocalDisplayName() {
|
||||
return await (await this.getLocalDisplayNameElement()).getText();
|
||||
return (await this.getLocalDisplayNameElement()).getText();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -546,7 +628,7 @@ export class Participant {
|
||||
* Returns the source of the large video currently shown.
|
||||
*/
|
||||
async getLargeVideoId() {
|
||||
return await this.driver.execute('return document.getElementById("largeVideo").srcObject.id');
|
||||
return this.driver.execute('return document.getElementById("largeVideo").srcObject.id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,4 +692,37 @@ export class Participant {
|
||||
expect(await displayNameEl.isDisplayed()).toBe(true);
|
||||
expect(await displayNameEl.getText()).toBe(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the leave reason dialog is open.
|
||||
*/
|
||||
async isLeaveReasonDialogOpen() {
|
||||
return this.driver.$('div[data-testid="dialog.leaveReason"]').isDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for remote video state - receiving and displayed.
|
||||
* @param endpointId
|
||||
*/
|
||||
async waitForRemoteVideo(endpointId: string) {
|
||||
await this.driver.waitUntil(async () =>
|
||||
await this.driver.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
endpointId) && await this.driver.$(
|
||||
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected remote video for ${endpointId} to be received 15s by ${this.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for ninja icon to be displayed.
|
||||
* @param endpointId
|
||||
*/
|
||||
async waitForNinjaIcon(endpointId: string) {
|
||||
await this.driver.$(`//span[@id='participant_${endpointId}']//span[@class='connection_ninja']`)
|
||||
.waitForDisplayed({
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected ninja icon for ${endpointId} to be displayed in 15s by ${this.name}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import jwt from 'jsonwebtoken';
|
||||
import process from 'node:process';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { Participant } from './Participant';
|
||||
import { P1_DISPLAY_NAME, P2_DISPLAY_NAME, P3_DISPLAY_NAME, P4_DISPLAY_NAME, Participant } from './Participant';
|
||||
import { IContext, IJoinOptions } from './types';
|
||||
|
||||
const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';
|
||||
@@ -34,13 +34,13 @@ export async function ensureThreeParticipants(ctx: IContext, options: IJoinOptio
|
||||
_joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: 'p2',
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
}),
|
||||
_joinParticipant('participant3', ctx.p3, p => {
|
||||
ctx.p3 = p;
|
||||
}, {
|
||||
displayName: 'p3',
|
||||
displayName: P3_DISPLAY_NAME,
|
||||
...options
|
||||
})
|
||||
]);
|
||||
@@ -68,19 +68,19 @@ export async function ensureFourParticipants(ctx: IContext, options: IJoinOption
|
||||
_joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: 'p2',
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
}),
|
||||
_joinParticipant('participant3', ctx.p3, p => {
|
||||
ctx.p3 = p;
|
||||
}, {
|
||||
displayName: 'p3',
|
||||
displayName: P3_DISPLAY_NAME,
|
||||
...options
|
||||
}),
|
||||
_joinParticipant('participant4', ctx.p4, p => {
|
||||
ctx.p4 = p;
|
||||
}, {
|
||||
displayName: 'p4',
|
||||
displayName: P4_DISPLAY_NAME,
|
||||
...options
|
||||
})
|
||||
]);
|
||||
@@ -102,7 +102,7 @@ export async function ensureFourParticipants(ctx: IContext, options: IJoinOption
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
|
||||
const p1DisplayName = 'p1';
|
||||
const p1DisplayName = P1_DISPLAY_NAME;
|
||||
let token;
|
||||
|
||||
// if it is jaas create the first one to be moderator and second not moderator
|
||||
@@ -135,7 +135,7 @@ export async function ensureTwoParticipants(ctx: IContext, options: IJoinOptions
|
||||
_joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: 'p2',
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
}),
|
||||
skipInMeetingChecks ? Promise.resolve() : ctx.p1.waitForRemoteStreams(1),
|
||||
|
||||
@@ -45,4 +45,10 @@ export type IJoinOptions = {
|
||||
* Whether to skip in meeting checks like ice connected and send receive data. For single in meeting participant.
|
||||
*/
|
||||
skipInMeetingChecks?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to skip waiting for the participant to join the room. Cases like lobby where we do not succeed to join
|
||||
* based on the logic of the test.
|
||||
*/
|
||||
skipWaitToJoin?: boolean;
|
||||
};
|
||||
|
||||
@@ -12,29 +12,29 @@ export default class AVModerationMenu extends BasePageObject {
|
||||
/**
|
||||
* Clicks the start audio moderation menu item.
|
||||
*/
|
||||
async clickStartAudioModeration() {
|
||||
await this.clickButton(START_AUDIO_MODERATION);
|
||||
clickStartAudioModeration() {
|
||||
return this.clickButton(START_AUDIO_MODERATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the stop audio moderation menu item.
|
||||
*/
|
||||
async clickStopAudioModeration() {
|
||||
await this.clickButton(STOP_AUDIO_MODERATION);
|
||||
clickStopAudioModeration() {
|
||||
return this.clickButton(STOP_AUDIO_MODERATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the start video moderation menu item.
|
||||
*/
|
||||
async clickStartVideoModeration() {
|
||||
await this.clickButton(START_VIDEO_MODERATION);
|
||||
clickStartVideoModeration() {
|
||||
return this.clickButton(START_VIDEO_MODERATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the stop audio moderation menu item.
|
||||
*/
|
||||
async clickStopVideoModeration() {
|
||||
await this.clickButton(STOP_VIDEO_MODERATION);
|
||||
clickStopVideoModeration() {
|
||||
return this.clickButton(STOP_VIDEO_MODERATION);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,14 +10,14 @@ export default class BaseDialog extends BasePageObject {
|
||||
/**
|
||||
* Clicks on the X (close) button.
|
||||
*/
|
||||
async clickCloseButton(): Promise<void> {
|
||||
await this.participant.driver.$(`#${CLOSE_BUTTON}`).click();
|
||||
clickCloseButton(): Promise<void> {
|
||||
return this.participant.driver.$(`#${CLOSE_BUTTON}`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the ok button.
|
||||
*/
|
||||
async clickOkButton(): Promise<void> {
|
||||
await this.participant.driver.$(`#${OK_BUTTON}`).click();
|
||||
clickOkButton(): Promise<void> {
|
||||
return this.participant.driver.$(`#${OK_BUTTON}`).click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ class BreakoutRoom extends BasePageObject {
|
||||
/**
|
||||
* Collapses the breakout room.
|
||||
*/
|
||||
async collapse() {
|
||||
collapse() {
|
||||
const collapseElem = this.participant.driver.$(
|
||||
`div[data-testid="${this.id}"]`);
|
||||
|
||||
await collapseElem.click();
|
||||
return collapseElem.click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,21 +209,4 @@ export default class BreakoutRooms extends BasePageObject {
|
||||
await sendButton.waitForClickable();
|
||||
await sendButton.click();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Open context menu for given participant.
|
||||
// */
|
||||
// async openParticipantContextMenu(participant: Participant) {
|
||||
// const listItem = this.participant.driver.$(
|
||||
// `div[@id="participant-item-${await participant.getEndpointId()}"]`);
|
||||
//
|
||||
// await listItem.waitForDisplayed();
|
||||
// await listItem.moveTo();
|
||||
//
|
||||
// const button = listItem.$(`aria/${PARTICIPANT_MORE_LABEL}`);
|
||||
//
|
||||
// await button.waitForClickable();
|
||||
// await button.click();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export default class ChatPanel extends BasePageObject {
|
||||
/**
|
||||
* Is chat panel open.
|
||||
*/
|
||||
async isOpen() {
|
||||
return await this.participant.driver.$('#sideToolbarContainer').isExisting();
|
||||
isOpen() {
|
||||
return this.participant.driver.$('#sideToolbarContainer').isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,8 +70,8 @@ export default class Filmstrip extends BasePageObject {
|
||||
/**
|
||||
* Returns the local video id.
|
||||
*/
|
||||
async getLocalVideoId() {
|
||||
return await this.participant.driver.execute(
|
||||
getLocalVideoId() {
|
||||
return this.participant.driver.execute(
|
||||
'return document.getElementById("localVideo_container").srcObject.id');
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class Filmstrip extends BasePageObject {
|
||||
const elem = this.participant.driver.$(
|
||||
`//span[@id='participant_${endpointId}']//img[contains(@class,'userAvatar')]`);
|
||||
|
||||
return await elem.isExisting() ? elem.getAttribute('src') : null;
|
||||
return await elem.isExisting() ? await elem.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,10 +113,11 @@ export default class Filmstrip extends BasePageObject {
|
||||
* @private
|
||||
*/
|
||||
private async clickOnRemoteMenuLink(participantId: string, linkClassname: string, dialogConfirm: boolean) {
|
||||
const thumbnail = this.participant.driver.$(
|
||||
`//span[@id='participant_${participantId}']//span[@id='remotevideomenu']`);
|
||||
await this.participant.driver.$(`//span[@id='participant_${participantId}']`).moveTo();
|
||||
|
||||
await thumbnail.moveTo();
|
||||
await this.participant.driver.$(
|
||||
`//span[@id='participant_${participantId
|
||||
}']//span[@id='remotevideomenu']//div[@id='remote-video-menu-trigger']`).moveTo();
|
||||
|
||||
const popoverElement = this.participant.driver.$(
|
||||
`//div[contains(@class, 'popover')]//div[contains(@class, '${linkClassname}')]`);
|
||||
@@ -154,8 +155,8 @@ export default class Filmstrip extends BasePageObject {
|
||||
* Kicks a participant.
|
||||
* @param participantId
|
||||
*/
|
||||
async kickParticipant(participantId: string) {
|
||||
await this.clickOnRemoteMenuLink(participantId, 'kicklink', true);
|
||||
kickParticipant(participantId: string) {
|
||||
return this.clickOnRemoteMenuLink(participantId, 'kicklink', true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,8 +178,8 @@ export default class Filmstrip extends BasePageObject {
|
||||
/**
|
||||
* Checks whether the local self view is displayed or not.
|
||||
*/
|
||||
async assertSelfViewIsHidden(hidden: boolean) {
|
||||
await this.participant.driver.$(LOCAL_VIDEO_XPATH).waitForDisplayed({
|
||||
assertSelfViewIsHidden(hidden: boolean) {
|
||||
return this.participant.driver.$(LOCAL_VIDEO_XPATH).waitForDisplayed({
|
||||
reverse: hidden,
|
||||
timeout: 5000,
|
||||
timeoutMsg: `Local video thumbnail is${hidden ? '' : ' not'} displayed for ${this.participant.name}`
|
||||
@@ -200,8 +201,8 @@ export default class Filmstrip extends BasePageObject {
|
||||
* Asserts that the remote videos are hidden or not.
|
||||
* @param reverse
|
||||
*/
|
||||
async assertRemoteVideosHidden(reverse = false) {
|
||||
await this.participant.driver.waitUntil(
|
||||
assertRemoteVideosHidden(reverse = false) {
|
||||
return this.participant.driver.waitUntil(
|
||||
async () =>
|
||||
await this.participant.driver.$$('//div[@id="remoteVideos" and contains(@class, "hidden")]').length > 0,
|
||||
{
|
||||
@@ -210,4 +211,12 @@ export default class Filmstrip extends BasePageObject {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the displayed remote video thumbnails.
|
||||
*/
|
||||
async countVisibleThumbnails() {
|
||||
return (await this.participant.driver.$$('//div[@id="remoteVideos"]//span[contains(@class,"videocontainer")]')
|
||||
.filter(thumbnail => thumbnail.isDisplayed())).length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ export default class IframeAPI extends BasePageObject {
|
||||
* Returns the json object from the iframeAPI helper.
|
||||
* @param event
|
||||
*/
|
||||
async getEventResult(event: string): Promise<any> {
|
||||
return await this.participant.driver.execute(
|
||||
getEventResult(event: string): Promise<any> {
|
||||
return this.participant.driver.execute(
|
||||
eventName => {
|
||||
const result = window.jitsiAPI.test[eventName];
|
||||
|
||||
@@ -27,8 +27,8 @@ export default class IframeAPI extends BasePageObject {
|
||||
* Adds an event listener to the iframeAPI.
|
||||
* @param eventName The event name.
|
||||
*/
|
||||
async addEventListener(eventName: string) {
|
||||
return await this.participant.driver.execute(
|
||||
addEventListener(eventName: string) {
|
||||
return this.participant.driver.execute(
|
||||
(event, prefix) => {
|
||||
console.log(`${new Date().toISOString()} ${prefix} Adding listener for event: ${event}`);
|
||||
window.jitsiAPI.addListener(event, evt => {
|
||||
@@ -42,15 +42,15 @@ export default class IframeAPI extends BasePageObject {
|
||||
/**
|
||||
* Returns an array of available rooms and details of it.
|
||||
*/
|
||||
async getRoomsInfo() {
|
||||
return await this.participant.driver.execute(() => window.jitsiAPI.getRoomsInfo());
|
||||
getRoomsInfo() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.getRoomsInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of participants in the conference.
|
||||
*/
|
||||
async getNumberOfParticipants() {
|
||||
return await this.participant.driver.execute(() => window.jitsiAPI.getNumberOfParticipants());
|
||||
getNumberOfParticipants() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.getNumberOfParticipants());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,8 +58,8 @@ export default class IframeAPI extends BasePageObject {
|
||||
* @param command The command.
|
||||
* @param args The arguments.
|
||||
*/
|
||||
async executeCommand(command: string, ...args: any[]) {
|
||||
return await this.participant.driver.execute(
|
||||
executeCommand(command: string, ...args: any[]) {
|
||||
return this.participant.driver.execute(
|
||||
(commandName, commandArgs) =>
|
||||
window.jitsiAPI.executeCommand(commandName, ...commandArgs)
|
||||
, command, args);
|
||||
@@ -68,15 +68,14 @@ export default class IframeAPI extends BasePageObject {
|
||||
/**
|
||||
* Returns the current state of the participant's pane.
|
||||
*/
|
||||
async isParticipantsPaneOpen() {
|
||||
return await this.participant.driver.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
|
||||
isParticipantsPaneOpen() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the embedded Jitsi Meet conference.
|
||||
*/
|
||||
async dispose() {
|
||||
return await this.participant.driver.execute(() => window.jitsiAPI.dispose());
|
||||
dispose() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.dispose());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default class InviteDialog extends BaseDialog {
|
||||
/**
|
||||
* Checks if the dialog is open.
|
||||
*/
|
||||
async isOpen() {
|
||||
isOpen() {
|
||||
return this.participant.driver.$(`.${DIALOG_CONTAINER}`).isExisting();
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ export default class InviteDialog extends BaseDialog {
|
||||
/**
|
||||
* Gets the string that contains the dial in number for the current conference.
|
||||
*/
|
||||
async getDialInNumber() {
|
||||
return await this.getValueAfterColon(PHONE_NUMBER);
|
||||
getDialInNumber() {
|
||||
return this.getValueAfterColon(PHONE_NUMBER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
30
tests/pageobjects/LobbyScreen.ts
Normal file
30
tests/pageobjects/LobbyScreen.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import PreMeetingScreen from './PreMeetingScreen';
|
||||
|
||||
const DISPLAY_NAME_TEST_ID = 'lobby.nameField';
|
||||
const JOIN_BUTTON_TEST_ID = 'lobby.knockButton';
|
||||
|
||||
/**
|
||||
* Page object for the Lobby screen.
|
||||
*/
|
||||
export default class LobbyScreen extends PreMeetingScreen {
|
||||
/**
|
||||
* Returns the join button element.
|
||||
*/
|
||||
getJoinButton(): ChainablePromiseElement {
|
||||
return this.participant.driver.$(`[data-testid="${JOIN_BUTTON_TEST_ID}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name input element.
|
||||
*/
|
||||
getDisplayNameInput(): ChainablePromiseElement {
|
||||
return this.participant.driver.$(`[data-testid="${DISPLAY_NAME_TEST_ID}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for lobby screen to load.
|
||||
*/
|
||||
waitForLoading(): Promise<void> {
|
||||
return this.participant.driver.$('.lobby-screen').waitForDisplayed({ timeout: 4000 });
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,15 @@ const ASK_TO_UNMUTE_NOTIFICATION_ID = 'notify.hostAskedUnmute';
|
||||
const JOIN_ONE_TEST_ID = 'notify.connectedOneMember';
|
||||
const JOIN_TWO_TEST_ID = 'notify.connectedTwoMembers';
|
||||
const JOIN_MULTIPLE_TEST_ID = 'notify.connectedThreePlusMembers';
|
||||
const LOBBY_ACCESS_DENIED_TEST_ID = 'lobby.joinRejectedMessage';
|
||||
const LOBBY_ENABLED_TEST_ID = 'lobby.notificationLobbyEnabled';
|
||||
const LOBBY_KNOCKING_PARTICIPANT_NOTIFICATION_XPATH
|
||||
= '//div[@data-testid="notify.participantWantsToJoin"]/div/div/span';
|
||||
const LOBBY_NOTIFICATIONS_TITLE_TEST_ID = 'lobby.notificationTitle';
|
||||
const LOBBY_PARTICIPANT_ACCESS_DENIED_TEST_ID = 'lobby.notificationLobbyAccessDenied';
|
||||
const LOBBY_PARTICIPANT_ACCESS_GRANTED_TEST_ID = 'lobby.notificationLobbyAccessGranted';
|
||||
const LOBBY_PARTICIPANT_ADMIT_TEST_ID = 'participantsPane.actions.admit';
|
||||
const LOBBY_PARTICIPANT_REJECT_TEST_ID = 'participantsPane.actions.reject';
|
||||
const RAISE_HAND_NOTIFICATION_ID = 'notify.raisedHand';
|
||||
const REENABLE_SELF_VIEW_NOTIFICATION_ID = 'notify.selfViewTitle';
|
||||
const REENABLE_SELF_VIEW_CLOSE_NOTIFICATION = 'notify.selfViewTitle-dismiss';
|
||||
@@ -38,10 +47,10 @@ export default class Notifications extends BasePageObject {
|
||||
/**
|
||||
* Dismisses any join notifications.
|
||||
*/
|
||||
async dismissAnyJoinNotification() {
|
||||
await Promise.allSettled(
|
||||
dismissAnyJoinNotification() {
|
||||
return Promise.allSettled(
|
||||
[ `${JOIN_ONE_TEST_ID}-dismiss`, `${JOIN_TWO_TEST_ID}-dismiss`, `${JOIN_MULTIPLE_TEST_ID}-dismiss` ]
|
||||
.map(async id => this.participant.driver.$(`#${id}"]`).click()));
|
||||
.map(id => this.participant.driver.$(`#${id}"]`).click()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +68,155 @@ export default class Notifications extends BasePageObject {
|
||||
/**
|
||||
* Closes the self view notification.
|
||||
*/
|
||||
async closeReEnableSelfViewNotification() {
|
||||
await this.participant.driver.$(`div[data-testid="${REENABLE_SELF_VIEW_CLOSE_NOTIFICATION}"]`).click();
|
||||
closeReEnableSelfViewNotification() {
|
||||
return this.participant.driver.$(`div[data-testid="${REENABLE_SELF_VIEW_CLOSE_NOTIFICATION}"]`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* The notification on participants page when Lobby is being enabled or disabled.
|
||||
*/
|
||||
getLobbyEnabledText() {
|
||||
return this.getNotificationText(LOBBY_ENABLED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a specific lobby notification.
|
||||
* @param testId
|
||||
* @private
|
||||
*/
|
||||
private async closeLobbyNotification(testId: string) {
|
||||
const notification = this.participant.driver.$(`[data-testid="${testId}"]`);
|
||||
|
||||
await notification.waitForExist();
|
||||
await notification.waitForStable();
|
||||
|
||||
const closeButton
|
||||
= this.participant.driver.$(`[data-testid="${LOBBY_NOTIFICATIONS_TITLE_TEST_ID}"]`)
|
||||
.$('#close-notification');
|
||||
|
||||
await closeButton.moveTo();
|
||||
await closeButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the notification.
|
||||
*/
|
||||
closeLobbyEnabled() {
|
||||
return this.closeLobbyNotification(LOBBY_ENABLED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the knocking participant (the only one) that is displayed on the notification.
|
||||
*/
|
||||
async getKnockingParticipantName() {
|
||||
const knockingParticipantNotification
|
||||
= this.participant.driver.$('//div[@data-testid="notify.participantWantsToJoin"]/div/div/span');
|
||||
|
||||
await knockingParticipantNotification.waitForDisplayed({
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'Knocking participant notification not displayed'
|
||||
});
|
||||
|
||||
return await knockingParticipantNotification.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Admits the last knocking participant (it is the only one).
|
||||
*/
|
||||
async allowLobbyParticipant() {
|
||||
const admitButton
|
||||
= this.participant.driver.$(`[data-testid="${LOBBY_PARTICIPANT_ADMIT_TEST_ID}"]`);
|
||||
|
||||
await admitButton.waitForExist();
|
||||
await admitButton.waitForClickable();
|
||||
await admitButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* The notification that someone's access was approved.
|
||||
*/
|
||||
getLobbyParticipantAccessGranted() {
|
||||
return this.getNotificationText(LOBBY_PARTICIPANT_ACCESS_GRANTED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the notification.
|
||||
*/
|
||||
closeLobbyParticipantAccessGranted() {
|
||||
return this.closeLobbyNotification(LOBBY_PARTICIPANT_ACCESS_GRANTED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns notification text if the notification is found in the next few seconds.
|
||||
* @return the notification text.
|
||||
*/
|
||||
private async getNotificationText(testId: string) {
|
||||
const notificationElement = this.participant.driver.$(`[data-testid="${testId}"]`);
|
||||
|
||||
await notificationElement.waitForExist();
|
||||
|
||||
return await notificationElement.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects the last knocking participant (it is the only one).
|
||||
*/
|
||||
async rejectLobbyParticipant() {
|
||||
const admitButton
|
||||
= this.participant.driver.$(`[data-testid="${LOBBY_PARTICIPANT_REJECT_TEST_ID}"]`);
|
||||
|
||||
await admitButton.waitForExist();
|
||||
await admitButton.waitForClickable();
|
||||
await admitButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* The notification test that someone's access was denied.
|
||||
*/
|
||||
getLobbyParticipantAccessDenied() {
|
||||
return this.getNotificationText(LOBBY_PARTICIPANT_ACCESS_DENIED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the notification.
|
||||
*/
|
||||
closeLobbyParticipantAccessDenied() {
|
||||
return this.closeLobbyNotification(LOBBY_PARTICIPANT_ACCESS_DENIED_TEST_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the notification for access denied for entering the lobby is shown.
|
||||
*/
|
||||
async waitForLobbyAccessDeniedNotification() {
|
||||
const displayNameEl
|
||||
= this.participant.driver.$(`div[data-testid="${LOBBY_ACCESS_DENIED_TEST_ID}"]`);
|
||||
|
||||
await displayNameEl.waitForExist({ timeout: 2000 });
|
||||
await displayNameEl.waitForDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will wait 3 seconds for the knocking participants to disappear and return true or will return false.
|
||||
* @return <tt>true</tt> if the knocking participants list was not displayed.
|
||||
*/
|
||||
waitForHideOfKnockingParticipants() {
|
||||
return this.participant.driver.$(LOBBY_KNOCKING_PARTICIPANT_NOTIFICATION_XPATH)
|
||||
.waitForDisplayed({
|
||||
timeout: 3000,
|
||||
reverse: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes local notification, for the participant that was denied.
|
||||
*/
|
||||
async closeLocalLobbyAccessDenied() {
|
||||
await this.participant.driver.$('[data-testid="lobby.joinRejectedMessage"').waitForExist();
|
||||
|
||||
const dismissButton
|
||||
= this.participant.driver.$('[data-testid="lobby.joinRejectedTitle-dismiss"]');
|
||||
|
||||
await dismissButton.moveTo();
|
||||
await dismissButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ChainablePromiseElement } from 'webdriverio';
|
||||
|
||||
import { Participant } from '../helpers/Participant';
|
||||
|
||||
import AVModerationMenu from './AVModerationMenu';
|
||||
@@ -24,7 +26,7 @@ export default class ParticipantsPane extends BasePageObject {
|
||||
/**
|
||||
* Checks if the pane is open.
|
||||
*/
|
||||
async isOpen() {
|
||||
isOpen() {
|
||||
return this.participant.driver.$(`.${PARTICIPANTS_PANE}`).isExisting();
|
||||
}
|
||||
|
||||
@@ -184,4 +186,57 @@ export default class ParticipantsPane extends BasePageObject {
|
||||
await inviteButton.waitForDisplayed();
|
||||
await inviteButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the participant by name.
|
||||
* @param name - The name to look for.
|
||||
* @private
|
||||
*/
|
||||
private async findLobbyParticipantByName(name: string): Promise<ChainablePromiseElement> {
|
||||
return this.participant.driver.$$('//div[@id="lobby-list"]//div[starts-with(@id, "participant-item-")]')
|
||||
.find(async participant => (await participant.getText()).includes(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to click on the approve button and fails if it cannot be clicked.
|
||||
* @param participantNameToAdmit - the name of the participant to admit.
|
||||
*/
|
||||
async admitLobbyParticipant(participantNameToAdmit: string) {
|
||||
const participantToAdmit = await this.findLobbyParticipantByName(participantNameToAdmit);
|
||||
|
||||
await participantToAdmit.moveTo();
|
||||
|
||||
const participantIdToAdmit = (await participantToAdmit.getAttribute('id'))
|
||||
.substring('participant-item-'.length);
|
||||
const admitButton = this.participant.driver
|
||||
.$(`[data-testid="admit-${participantIdToAdmit}"]`);
|
||||
|
||||
await admitButton.waitForExist();
|
||||
await admitButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to click on the reject button and fails if it cannot be clicked.
|
||||
* @param participantNameToReject - the name of the participant for this {@link ParticipantsPane} to reject.
|
||||
*/
|
||||
async rejectLobbyParticipant(participantNameToReject: string) {
|
||||
const participantToReject
|
||||
= await this.findLobbyParticipantByName(participantNameToReject);
|
||||
|
||||
await participantToReject.moveTo();
|
||||
|
||||
const participantIdToReject = (await participantToReject.getAttribute('id'))
|
||||
.substring('participant-item-'.length);
|
||||
|
||||
const moreOptionsButton
|
||||
= this.participant.driver.$(`aria/More moderation options ${participantNameToReject}`);
|
||||
|
||||
await moreOptionsButton.click();
|
||||
|
||||
const rejectButton = this.participant.driver
|
||||
.$(`[data-testid="reject-${participantIdToReject}"]`);
|
||||
|
||||
await rejectButton.waitForExist();
|
||||
await rejectButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
31
tests/pageobjects/PreJoinScreen.ts
Normal file
31
tests/pageobjects/PreJoinScreen.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import PreMeetingScreen from './PreMeetingScreen';
|
||||
|
||||
const DISPLAY_NAME_ID = 'premeeting-name-input';
|
||||
const JOIN_BUTTON_TEST_ID = 'prejoin.joinMeeting';
|
||||
|
||||
/**
|
||||
* Page object for the PreJoin screen.
|
||||
*/
|
||||
export default class PreJoinScreen extends PreMeetingScreen {
|
||||
/**
|
||||
* Returns the join button element.
|
||||
*/
|
||||
getJoinButton(): ChainablePromiseElement {
|
||||
return this.participant.driver.$(`[data-testid="${JOIN_BUTTON_TEST_ID}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display name input element.
|
||||
*/
|
||||
getDisplayNameInput(): ChainablePromiseElement {
|
||||
return this.participant.driver.$(`#${DISPLAY_NAME_ID}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for pre join screen to load.
|
||||
*/
|
||||
waitForLoading(): Promise<void> {
|
||||
return this.participant.driver.$('[data-testid="prejoin.screen"]')
|
||||
.waitForDisplayed({ timeout: 3000 });
|
||||
}
|
||||
}
|
||||
90
tests/pageobjects/PreMeetingScreen.ts
Normal file
90
tests/pageobjects/PreMeetingScreen.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import BasePageObject from './BasePageObject';
|
||||
|
||||
const PASSWORD_BUTTON_TEST_ID = 'lobby.enterPasswordButton';
|
||||
|
||||
/**
|
||||
* Page object for the PreMeeting screen, common stuff between pre-join and lobby screens.
|
||||
*/
|
||||
export default abstract class PreMeetingScreen extends BasePageObject {
|
||||
/**
|
||||
* Waits for pre join or lobby screen to load.
|
||||
*/
|
||||
abstract waitForLoading(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns the display name input element.
|
||||
*/
|
||||
abstract getDisplayNameInput(): WebdriverIO.Element;
|
||||
|
||||
/**
|
||||
* Returns the join button element.
|
||||
*/
|
||||
abstract getJoinButton(): WebdriverIO.Element;
|
||||
|
||||
/**
|
||||
* Interacts with the view to enter a display name.
|
||||
*/
|
||||
async enterDisplayName(displayName: string) {
|
||||
const displayNameInput = this.getDisplayNameInput();
|
||||
|
||||
await displayNameInput.click();
|
||||
|
||||
// element.clear does not always work, make sure we delete the content
|
||||
await displayNameInput.clearValue();
|
||||
|
||||
await this.participant.driver.keys(displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks internally whether lobby room is joined.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
waitToJoinLobby(): Promise<void> {
|
||||
return this.participant.driver.waitUntil(
|
||||
() => this.isLobbyRoomJoined(),
|
||||
{
|
||||
timeout: 3_000, // 3 seconds
|
||||
timeoutMsg: `Timeout waiting to join lobby for ${this.participant.name}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks internally whether lobby room is joined.
|
||||
*/
|
||||
isLobbyRoomJoined() {
|
||||
return this.participant.driver.execute(
|
||||
() => APP.conference._room?.room?.getLobby()?.lobbyRoom?.joined === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password button element.
|
||||
*/
|
||||
getPasswordButton() {
|
||||
return this.participant.driver.$(`[data-testid="${PASSWORD_BUTTON_TEST_ID}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interacts with the view to enter a password.
|
||||
*/
|
||||
async enterPassword(password: string) {
|
||||
const passwordButton = this.getPasswordButton();
|
||||
|
||||
await passwordButton.moveTo();
|
||||
await passwordButton.click();
|
||||
|
||||
const passwordInput = this.participant.driver.$('[data-testid="lobby.password"]');
|
||||
|
||||
await passwordInput.waitForDisplayed();
|
||||
await passwordInput.click();
|
||||
await passwordInput.clearValue();
|
||||
|
||||
await this.participant.driver.keys(password);
|
||||
|
||||
const joinButton = this.participant.driver.$('[data-testid="lobby.passwordJoinButton"]');
|
||||
|
||||
await joinButton.waitForDisplayed();
|
||||
await joinButton.click();
|
||||
}
|
||||
}
|
||||
136
tests/pageobjects/SecurityDialog.ts
Normal file
136
tests/pageobjects/SecurityDialog.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Key } from 'webdriverio';
|
||||
|
||||
import BaseDialog from './BaseDialog';
|
||||
|
||||
const ADD_PASSWORD_LINK = 'add-password';
|
||||
const ADD_PASSWORD_FIELD = 'info-password-input';
|
||||
const DIALOG_CONTAINER = 'security-dialog';
|
||||
const LOCAL_LOCK = 'info-password-local';
|
||||
const REMOTE_LOCK = 'info-password-remote';
|
||||
|
||||
/**
|
||||
* Page object for the security dialog.
|
||||
*/
|
||||
export default class SecurityDialog extends BaseDialog {
|
||||
/**
|
||||
* Waits for the settings dialog to be visible.
|
||||
*/
|
||||
waitForDisplay() {
|
||||
return this.participant.driver.$(`.${DIALOG_CONTAINER}`).waitForDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the switch that can be used to detect lobby state or change lobby state.
|
||||
* @private
|
||||
*/
|
||||
private getLobbySwitch() {
|
||||
return this.participant.driver.$('#lobby-section-switch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns is the lobby enabled.
|
||||
*/
|
||||
isLobbyEnabled() {
|
||||
return this.getLobbySwitch().isSelected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the lobby option from the security dialog.
|
||||
*/
|
||||
async toggleLobby() {
|
||||
const lobbySwitch = this.getLobbySwitch();
|
||||
|
||||
await lobbySwitch.moveTo();
|
||||
await lobbySwitch.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether lobby section is present in the UI.
|
||||
*/
|
||||
isLobbySectionPresent() {
|
||||
return this.getLobbySwitch().isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the lobby to be enabled or disabled.
|
||||
* @param reverse
|
||||
*/
|
||||
waitForLobbyEnabled(reverse = false) {
|
||||
const lobbySwitch = this.getLobbySwitch();
|
||||
|
||||
return this.participant.driver.waitUntil(
|
||||
async () => await lobbySwitch.isSelected() !== reverse,
|
||||
{
|
||||
timeout: 5_000, // 30 seconds
|
||||
timeoutMsg: `Timeout waiting for lobby being ${reverse ? 'disabled' : 'enabled'} for ${
|
||||
this.participant.name}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current conference is locked with a locally set password.
|
||||
*
|
||||
* @return {@code true} if the conference is displayed as locked locally in
|
||||
* the security dialog, {@code false} otherwise.
|
||||
*/
|
||||
private isLockedLocally() {
|
||||
return this.participant.driver.$(`.${LOCAL_LOCK}`).isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current conference is locked with a locally set password.
|
||||
*
|
||||
* @return {@code true} if the conference is displayed as locked remotely
|
||||
* in the security dialog, {@code false} otherwise.
|
||||
*/
|
||||
private isLockedRemotely() {
|
||||
return this.participant.driver.$(`.${REMOTE_LOCK}`).isExisting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current conference is locked based on the security dialog's
|
||||
* display state.
|
||||
*
|
||||
* @return {@code true} if the conference is displayed as locked in the
|
||||
* security dialog, {@code false} otherwise.
|
||||
*/
|
||||
async isLocked() {
|
||||
return await this.isLockedLocally() || await this.isLockedRemotely();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a password on the current conference to lock it.
|
||||
*
|
||||
* @param password - The password to use to lock the conference.
|
||||
*/
|
||||
async addPassword(password: string) {
|
||||
const addPasswordLink = this.participant.driver.$(`.${ADD_PASSWORD_LINK}`);
|
||||
|
||||
await addPasswordLink.waitForClickable();
|
||||
await addPasswordLink.click();
|
||||
|
||||
const passwordEntry = this.participant.driver.$(`#${ADD_PASSWORD_FIELD}`);
|
||||
|
||||
await passwordEntry.waitForDisplayed();
|
||||
await passwordEntry.click();
|
||||
|
||||
await this.participant.driver.keys(password);
|
||||
await this.participant.driver.$('button=Add').click();
|
||||
|
||||
let validationMessage;
|
||||
|
||||
// There are two cases here, validation is enabled and the field passwordEntry maybe there
|
||||
// with validation failed, or maybe successfully hidden after setting the password
|
||||
// So let's give it some time to act on any of the above
|
||||
if (!await passwordEntry.isExisting()) {
|
||||
// validation had failed on password field as it is still on the page
|
||||
validationMessage = passwordEntry.getAttribute('validationMessage');
|
||||
}
|
||||
|
||||
if (validationMessage) {
|
||||
await this.participant.driver.keys([ Key.Escape ]);
|
||||
expect(validationMessage).toBe('');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ export default class SettingsDialog extends BaseDialog {
|
||||
/**
|
||||
* Waits for the settings dialog to be visible.
|
||||
*/
|
||||
async waitForDisplay() {
|
||||
await this.participant.driver.$(SETTINGS_DIALOG_CONTENT).waitForDisplayed();
|
||||
waitForDisplay() {
|
||||
return this.participant.driver.$(SETTINGS_DIALOG_CONTENT).waitForDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,22 +34,22 @@ export default class SettingsDialog extends BaseDialog {
|
||||
/**
|
||||
* Selects the Profile tab to be displayed.
|
||||
*/
|
||||
async openProfileTab() {
|
||||
await this.openTab(X_PATH_PROFILE_TAB);
|
||||
openProfileTab() {
|
||||
return this.openTab(X_PATH_PROFILE_TAB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the More tab to be displayed.
|
||||
*/
|
||||
async openMoreTab() {
|
||||
await this.openTab(X_PATH_MORE_TAB);
|
||||
openMoreTab() {
|
||||
return this.openTab(X_PATH_MORE_TAB);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the moderator tab to be displayed.
|
||||
*/
|
||||
async openModeratorTab() {
|
||||
await this.openTab(X_PATH_MODERATOR_TAB);
|
||||
openModeratorTab() {
|
||||
return this.openTab(X_PATH_MODERATOR_TAB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,8 +74,8 @@ export default class SettingsDialog extends BaseDialog {
|
||||
/**
|
||||
* Clicks the OK button on the settings dialog to close the dialog and save any changes made.
|
||||
*/
|
||||
async submit() {
|
||||
await this.clickOkButton();
|
||||
submit() {
|
||||
return this.clickOkButton();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ const OVERFLOW = 'More actions';
|
||||
const PARTICIPANTS = 'Open participants pane';
|
||||
const PROFILE = 'Edit your profile';
|
||||
const RAISE_HAND = 'Raise your hand';
|
||||
const SECURITY = 'Security options';
|
||||
const SETTINGS = 'Open settings';
|
||||
const STOP_DESKTOP = 'Stop sharing your screen';
|
||||
const ENTER_TILE_VIEW_BUTTON = 'Enter tile view';
|
||||
@@ -54,9 +55,10 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickAudioMuteButton(): Promise<void> {
|
||||
clickAudioMuteButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Audio Mute Button');
|
||||
await this.audioMuteBtn.click();
|
||||
|
||||
return this.audioMuteBtn.click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,9 +66,10 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickAudioUnmuteButton(): Promise<void> {
|
||||
clickAudioUnmuteButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Audio Unmute Button');
|
||||
await this.audioUnMuteBtn.click();
|
||||
|
||||
return this.audioUnMuteBtn.click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,9 +91,10 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickVideoMuteButton(): Promise<void> {
|
||||
clickVideoMuteButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Video Mute Button');
|
||||
await this.videoMuteBtn.click();
|
||||
|
||||
return this.videoMuteBtn.click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,9 +102,10 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickVideoUnmuteButton(): Promise<void> {
|
||||
clickVideoUnmuteButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Video Unmute Button');
|
||||
await this.videoUnMuteBtn.click();
|
||||
|
||||
return this.videoUnMuteBtn.click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,9 +113,10 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickCloseParticipantsPaneButton(): Promise<void> {
|
||||
clickCloseParticipantsPaneButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Close Participants pane Button');
|
||||
await this.getButton(CLOSE_PARTICIPANTS_PANE).click();
|
||||
|
||||
return this.getButton(CLOSE_PARTICIPANTS_PANE).click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,94 +124,105 @@ export default class Toolbar extends BasePageObject {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clickParticipantsPaneButton(): Promise<void> {
|
||||
clickParticipantsPaneButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Participants pane Button');
|
||||
|
||||
// Special case for participants pane button, as it contains the number of participants and its label
|
||||
// is changing
|
||||
await this.participant.driver.$(`[aria-label^="${PARTICIPANTS}"]`).click();
|
||||
return this.participant.driver.$(`[aria-label^="${PARTICIPANTS}"]`).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the video quality toolbar button which opens the
|
||||
* dialog for adjusting max-received video quality.
|
||||
*/
|
||||
async clickVideoQualityButton(): Promise<void> {
|
||||
clickVideoQualityButton(): Promise<void> {
|
||||
return this.clickButtonInOverflowMenu(VIDEO_QUALITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the profile toolbar button which opens or closes the profile panel.
|
||||
*/
|
||||
async clickProfileButton(): Promise<void> {
|
||||
clickProfileButton(): Promise<void> {
|
||||
return this.clickButtonInOverflowMenu(PROFILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the raise hand button that enables participants will to speak.
|
||||
*/
|
||||
async clickRaiseHandButton(): Promise<void> {
|
||||
clickRaiseHandButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Raise hand Button');
|
||||
await this.getButton(RAISE_HAND).click();
|
||||
|
||||
return this.getButton(RAISE_HAND).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the chat button that opens chat panel.
|
||||
*/
|
||||
async clickChatButton(): Promise<void> {
|
||||
clickChatButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Chat Button');
|
||||
await this.getButton(CHAT).click();
|
||||
|
||||
return this.getButton(CHAT).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the chat button that closes chat panel.
|
||||
*/
|
||||
async clickCloseChatButton(): Promise<void> {
|
||||
clickCloseChatButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Close Chat Button');
|
||||
await this.getButton(CLOSE_CHAT).click();
|
||||
|
||||
return this.getButton(CLOSE_CHAT).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the desktop sharing button that starts desktop sharing.
|
||||
*/
|
||||
async clickDesktopSharingButton() {
|
||||
await this.getButton(DESKTOP).click();
|
||||
clickDesktopSharingButton() {
|
||||
return this.getButton(DESKTOP).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the desktop sharing button to stop it.
|
||||
*/
|
||||
async clickStopDesktopSharingButton() {
|
||||
await this.getButton(STOP_DESKTOP).click();
|
||||
clickStopDesktopSharingButton() {
|
||||
return this.getButton(STOP_DESKTOP).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the tile view button which enables tile layout.
|
||||
*/
|
||||
async clickEnterTileViewButton() {
|
||||
await this.getButton(ENTER_TILE_VIEW_BUTTON).click();
|
||||
clickEnterTileViewButton() {
|
||||
return this.getButton(ENTER_TILE_VIEW_BUTTON).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the tile view button which exits tile layout.
|
||||
*/
|
||||
async clickExitTileViewButton() {
|
||||
await this.getButton(EXIT_TILE_VIEW_BUTTON).click();
|
||||
clickExitTileViewButton() {
|
||||
return this.getButton(EXIT_TILE_VIEW_BUTTON).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the hangup button that ends the conference.
|
||||
*/
|
||||
async clickHangupButton(): Promise<void> {
|
||||
clickHangupButton(): Promise<void> {
|
||||
this.participant.log('Clicking on: Hangup Button');
|
||||
await this.getButton(HANGUP).click();
|
||||
|
||||
return this.getButton(HANGUP).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the security toolbar button which opens the security panel.
|
||||
*/
|
||||
clickSecurityButton() {
|
||||
return this.clickButtonInOverflowMenu(SECURITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the settings toolbar button which opens or closes the settings panel.
|
||||
*/
|
||||
async clickSettingsButton() {
|
||||
await this.clickButtonInOverflowMenu(SETTINGS);
|
||||
clickSettingsButton() {
|
||||
return this.clickButtonInOverflowMenu(SETTINGS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,8 +255,8 @@ export default class Toolbar extends BasePageObject {
|
||||
* Clicks on the overflow toolbar button which opens or closes the overflow menu.
|
||||
* @private
|
||||
*/
|
||||
private async clickOverflowButton(): Promise<void> {
|
||||
await this.getButton(OVERFLOW).click();
|
||||
private clickOverflowButton(): Promise<void> {
|
||||
return this.getButton(OVERFLOW).click();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,8 +292,8 @@ export default class Toolbar extends BasePageObject {
|
||||
* @param visible
|
||||
* @private
|
||||
*/
|
||||
private async waitForOverFlowMenu(visible: boolean) {
|
||||
await this.getButton(OVERFLOW_MENU).waitForDisplayed({
|
||||
private waitForOverFlowMenu(visible: boolean) {
|
||||
return this.getButton(OVERFLOW_MENU).waitForDisplayed({
|
||||
reverse: !visible,
|
||||
timeout: 3000,
|
||||
timeoutMsg: `Overflow menu is not ${visible ? 'visible' : 'hidden'}`
|
||||
|
||||
BIN
tests/resources/fakeAudioStream.wav
Normal file
BIN
tests/resources/fakeAudioStream.wav
Normal file
Binary file not shown.
9
tests/resources/fakeAudioStream_license.txt
Normal file
9
tests/resources/fakeAudioStream_license.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
fakeAudioStream.wav created from [1].
|
||||
|
||||
Speaker: The Jacobin
|
||||
Derivative of [2]
|
||||
Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)[3]
|
||||
|
||||
[1]: https://en.wikipedia.org/wiki/File:Helium.ogg
|
||||
[2]: https://en.wikipedia.org/wiki/Helium?oldid=302049858
|
||||
[3]: https://creativecommons.org/licenses/by-sa/3.0/deed.en
|
||||
@@ -1,18 +1,12 @@
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Audio only - ', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
skipFirstModerator: true
|
||||
});
|
||||
});
|
||||
describe('Audio only', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants(ctx, { skipFirstModerator: true }));
|
||||
|
||||
/**
|
||||
* Enables audio only mode for p1 and verifies that the other participant sees participant1 as video muted.
|
||||
*/
|
||||
it('set and check', async () => {
|
||||
await setAudioOnlyAndCheck(true);
|
||||
});
|
||||
it('set and check', () => setAudioOnlyAndCheck(true));
|
||||
|
||||
/**
|
||||
* Verifies that participant1 sees avatars for itself and other participants.
|
||||
@@ -29,9 +23,7 @@ describe('Audio only - ', () => {
|
||||
/**
|
||||
* Disables audio only mode and verifies that both participants see p1 as not video muted.
|
||||
*/
|
||||
it('disable and check', async () => {
|
||||
await setAudioOnlyAndCheck(false);
|
||||
});
|
||||
it('disable and check', () => setAudioOnlyAndCheck(false));
|
||||
|
||||
/**
|
||||
* Mutes video on participant1, toggles audio-only twice and then verifies if both participants see participant1
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('DisplayName - ', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
skipDisplayName: true
|
||||
});
|
||||
});
|
||||
describe('DisplayName', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants(ctx, { skipDisplayName: true }));
|
||||
|
||||
it('check change', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('End Conference - ', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
});
|
||||
describe('End Conference', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants(ctx));
|
||||
|
||||
it('hangup call and check', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
@@ -3,7 +3,7 @@ import process from 'node:process';
|
||||
import { ensureOneParticipant, ensureTwoParticipants } from '../../helpers/participants';
|
||||
import { cleanup, isDialInEnabled, waitForAudioFromDialInParticipant } from '../helpers/DialIn';
|
||||
|
||||
describe('Fake Dial-In - ', () => {
|
||||
describe('Fake Dial-In', () => {
|
||||
it('join participant', async () => {
|
||||
// we execute fake dial in only if the real dial in is not enabled
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { P1_DISPLAY_NAME, P2_DISPLAY_NAME, Participant } from '../../helpers/Participant';
|
||||
import { ensureTwoParticipants, parseJid } from '../../helpers/participants';
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ async function checkParticipantLeftHook(p: Participant, reason: string) {
|
||||
}
|
||||
}
|
||||
|
||||
describe('Participants presence - ', () => {
|
||||
describe('Participants presence', () => {
|
||||
it('joining the meeting', async () => {
|
||||
// ensure 2 participants one moderator and one guest, we will load both with iframeAPI
|
||||
await ensureTwoParticipants(ctx);
|
||||
@@ -144,7 +144,7 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await p1.getIframeAPI().executeCommand('grantModerator', p2EpId);
|
||||
|
||||
await p2.driver.waitUntil(async () => await p2.getIframeAPI().getEventResult('isModerator'), {
|
||||
await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('isModerator'), {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'Moderator role not granted'
|
||||
});
|
||||
@@ -202,13 +202,11 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await p1.getIframeAPI().executeCommand('kickParticipant', p2EpId);
|
||||
|
||||
const eventP1 = await p1.driver.waitUntil(async () =>
|
||||
await p1.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
const eventP1 = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on participant1 side'
|
||||
});
|
||||
const eventP2 = await p2.driver.waitUntil(async () =>
|
||||
await p2.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
const eventP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('participantKickedOut'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantKickedOut event not received on participant2 side'
|
||||
});
|
||||
@@ -243,8 +241,8 @@ describe('Participants presence - ', () => {
|
||||
}
|
||||
})).toBe(true);
|
||||
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(async () =>
|
||||
await p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(() =>
|
||||
p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
@@ -289,8 +287,7 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await p1.switchToAPI();
|
||||
|
||||
const event = await p1.driver.waitUntil(async () =>
|
||||
await p1.getIframeAPI().getEventResult('participantJoined'), {
|
||||
const event = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('participantJoined'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'participantJoined not received'
|
||||
});
|
||||
@@ -310,8 +307,8 @@ describe('Participants presence - ', () => {
|
||||
const p1EpId = await p1.getEndpointId();
|
||||
const p2EpId = await p2.getEndpointId();
|
||||
|
||||
const newP1Name = 'p1';
|
||||
const newP2Name = 'p2';
|
||||
const newP1Name = P1_DISPLAY_NAME;
|
||||
const newP2Name = P2_DISPLAY_NAME;
|
||||
const newNames: ({ id: string; name: string; })[] = [ {
|
||||
id: p2EpId,
|
||||
name: newP2Name
|
||||
@@ -341,8 +338,8 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await p2.getIframeAPI().executeCommand('hangup');
|
||||
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(async () =>
|
||||
await p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
const eventConferenceLeftP2 = await p2.driver.waitUntil(() =>
|
||||
p2.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
@@ -352,8 +349,7 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await checkParticipantLeftHook(p2, 'left');
|
||||
|
||||
const eventReadyToCloseP2 = await p2.driver.waitUntil(async () =>
|
||||
await p2.getIframeAPI().getEventResult('readyToClose'), {
|
||||
const eventReadyToCloseP2 = await p2.driver.waitUntil(() => p2.getIframeAPI().getEventResult('readyToClose'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'readyToClose not received'
|
||||
});
|
||||
@@ -371,8 +367,8 @@ describe('Participants presence - ', () => {
|
||||
|
||||
await p1.getIframeAPI().executeCommand('hangup');
|
||||
|
||||
const eventConferenceLeft = await p1.driver.waitUntil(async () =>
|
||||
await p1.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
const eventConferenceLeft = await p1.driver.waitUntil(() =>
|
||||
p1.getIframeAPI().getEventResult('videoConferenceLeft'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'videoConferenceLeft not received'
|
||||
});
|
||||
@@ -397,8 +393,7 @@ describe('Participants presence - ', () => {
|
||||
expect(event.data.isBreakout).toBe(false);
|
||||
}
|
||||
|
||||
const eventReadyToClose = await p1.driver.waitUntil(async () =>
|
||||
await p1.getIframeAPI().getEventResult('readyToClose'), {
|
||||
const eventReadyToClose = await p1.driver.waitUntil(() => p1.getIframeAPI().getEventResult('readyToClose'), {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'readyToClose not received'
|
||||
});
|
||||
|
||||
39
tests/specs/2way/kick.spec.ts
Normal file
39
tests/specs/2way/kick.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Kick', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
if (!await ctx.p1.isModerator()) {
|
||||
ctx.skipSuiteTests = true;
|
||||
}
|
||||
});
|
||||
|
||||
it('kick and check', () => kickParticipant2AndCheck());
|
||||
|
||||
it('kick p2p and check', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
configOverwrite: {
|
||||
p2p: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await kickParticipant2AndCheck();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Kicks the second participant and checks that the participant is removed from the conference and that dialog is open.
|
||||
*/
|
||||
async function kickParticipant2AndCheck() {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await p1.getFilmstrip().kickParticipant(await p2.getEndpointId());
|
||||
|
||||
await p1.waitForParticipants(0);
|
||||
|
||||
// check that the kicked participant sees the kick reason dialog
|
||||
expect(await p2.isLeaveReasonDialogOpen()).toBe(true);
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Self view - ', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
});
|
||||
describe('Self view', () => {
|
||||
it('joining the meeting', () => ensureTwoParticipants(ctx));
|
||||
|
||||
it('hide from menu', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { Participant } from '../../helpers/Participant';
|
||||
import { ensureThreeParticipants, muteAudioAndCheck } from '../../helpers/participants';
|
||||
|
||||
describe('ActiveSpeaker ', () => {
|
||||
describe('ActiveSpeaker', () => {
|
||||
it('testActiveSpeaker', async () => {
|
||||
await ensureThreeParticipants(ctx);
|
||||
|
||||
@@ -61,8 +61,7 @@ async function testActiveSpeaker(
|
||||
const otherParticipant1Driver = otherParticipant1.driver;
|
||||
|
||||
await otherParticipant1Driver.waitUntil(
|
||||
async () => await otherParticipant1Driver.execute(
|
||||
id => APP.UI.getLargeVideoID() === id, speakerEndpoint),
|
||||
() => otherParticipant1Driver.execute(id => APP.UI.getLargeVideoID() === id, speakerEndpoint),
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: 'Active speaker not displayed on large video.'
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
unmuteVideoAndCheck
|
||||
} from '../../helpers/participants';
|
||||
|
||||
describe('AVModeration -', () => {
|
||||
describe('AVModeration', () => {
|
||||
|
||||
it('check for moderators', async () => {
|
||||
// if all 3 participants are moderators, skip this test
|
||||
@@ -135,7 +135,7 @@ describe('AVModeration -', () => {
|
||||
await p1.getFilmstrip().grantModerator(p3);
|
||||
|
||||
await p3.driver.waitUntil(
|
||||
async () => await p3.isModerator(), {
|
||||
() => p3.isModerator(), {
|
||||
timeout: 5000,
|
||||
timeoutMsg: `${p3.name} is not moderator`
|
||||
});
|
||||
@@ -190,6 +190,7 @@ describe('AVModeration -', () => {
|
||||
await p1ParticipantsPane.clickContextMenuButton();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartAudioModeration();
|
||||
await p1ParticipantsPane.getAVModerationMenu().clickStartVideoModeration();
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
// join with second participant and check
|
||||
await ensureTwoParticipants(ctx, {
|
||||
|
||||
@@ -7,16 +7,15 @@ import {
|
||||
const EMAIL = 'support@jitsi.org';
|
||||
const HASH = '38f014e4b7dde0f64f8157d26a8c812e';
|
||||
|
||||
describe('Avatar - ', () => {
|
||||
it('setup the meeting', async () => {
|
||||
// Start p1
|
||||
await ensureTwoParticipants(ctx, {
|
||||
describe('Avatar', () => {
|
||||
it('setup the meeting', () =>
|
||||
ensureTwoParticipants(ctx, {
|
||||
skipDisplayName: true,
|
||||
|
||||
// no default avatar if we have used to join a token with an avatar and no option to set it
|
||||
skipFirstModerator: true
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
it('change and check', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
@@ -7,7 +7,7 @@ const MAIN_ROOM_NAME = 'Main room';
|
||||
const BREAKOUT_ROOMS_LIST_ID = 'breakout-rooms-list';
|
||||
const LIST_ITEM_CONTAINER = 'list-item-container';
|
||||
|
||||
describe('BreakoutRooms ', () => {
|
||||
describe('BreakoutRooms', () => {
|
||||
it('check support', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
@@ -266,8 +266,8 @@ describe('BreakoutRooms ', () => {
|
||||
});
|
||||
|
||||
// there should be two participants in the main room, either p2 or p3 got moved to the main room
|
||||
const checkParticipants = async (p: Participant) => {
|
||||
await p.driver.waitUntil(
|
||||
const checkParticipants = (p: Participant) =>
|
||||
p.driver.waitUntil(
|
||||
async () => {
|
||||
const isInBreakoutRoom = await p.isInBreakoutRoom();
|
||||
const breakoutRooms = p.getBreakoutRooms();
|
||||
@@ -303,7 +303,6 @@ describe('BreakoutRooms ', () => {
|
||||
timeout: 2000,
|
||||
timeoutMsg: `${p.name} is not seeing an empty breakout room and one with one participant`
|
||||
});
|
||||
};
|
||||
|
||||
await checkParticipants(p2);
|
||||
await checkParticipants(p3);
|
||||
@@ -421,8 +420,10 @@ describe('BreakoutRooms ', () => {
|
||||
|
||||
await checkSubject(p2, myNewRoomName);
|
||||
|
||||
const p2BreakoutRooms = p2.getBreakoutRooms();
|
||||
|
||||
// leave room
|
||||
await p2.getBreakoutRooms().leaveBreakoutRoom();
|
||||
await p2BreakoutRooms.leaveBreakoutRoom();
|
||||
|
||||
// there should be one empty room
|
||||
await p1.driver.waitUntil(
|
||||
@@ -436,10 +437,20 @@ describe('BreakoutRooms ', () => {
|
||||
return list[0].participantCount === 0;
|
||||
}, {
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'The breakout room was not renamed for p1'
|
||||
timeoutMsg: 'The breakout room not found or not empty for p1'
|
||||
});
|
||||
|
||||
expect((await p2.getBreakoutRooms().getRooms())[0].name).toBe(myNewRoomName);
|
||||
await p2.driver.waitUntil(
|
||||
async () => {
|
||||
const list = await p2BreakoutRooms.getRooms();
|
||||
|
||||
return list?.length === 1;
|
||||
}, {
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'The breakout room not seen by p2'
|
||||
});
|
||||
|
||||
expect((await p2BreakoutRooms.getRooms())[0].name).toBe(myNewRoomName);
|
||||
|
||||
// send the second participant to the first breakout room
|
||||
await p1BreakoutRooms.sendParticipantToBreakoutRoom(p2, myNewRoomName);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ensureOneParticipant, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Codec selection - ', () => {
|
||||
describe('Codec selection', () => {
|
||||
it('asymmetric codecs', async () => {
|
||||
await ensureOneParticipant(ctx, {
|
||||
configOverwrite: {
|
||||
@@ -104,14 +104,14 @@ describe('Codec selection - ', () => {
|
||||
|
||||
// Check of p1 and p2 have switched to VP9.
|
||||
await p1.driver.waitUntil(
|
||||
async () => await p1.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
|
||||
() => 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()),
|
||||
() => p2.driver.execute(() => JitsiMeetJS.app.testing.isLocalCameraEncodingVp9()),
|
||||
{
|
||||
timeout: 10000,
|
||||
timeoutMsg: 'p1 did not switch back to VP9'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('Follow Me - ', () => {
|
||||
describe('Follow Me', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
|
||||
487
tests/specs/3way/lobby.spec.ts
Normal file
487
tests/specs/3way/lobby.spec.ts
Normal file
@@ -0,0 +1,487 @@
|
||||
import { P1_DISPLAY_NAME, P3_DISPLAY_NAME, Participant } from '../../helpers/Participant';
|
||||
import { ensureOneParticipant, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
import type { IJoinOptions } from '../../helpers/types';
|
||||
import type PreMeetingScreen from '../../pageobjects/PreMeetingScreen';
|
||||
|
||||
describe('Lobby', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
|
||||
if (!await ctx.p1.driver.execute(() => APP.conference._room.isLobbySupported())) {
|
||||
ctx.skipSuiteTests = true;
|
||||
}
|
||||
});
|
||||
|
||||
it('enable', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
await enableLobby();
|
||||
});
|
||||
|
||||
it('entering in lobby and approve', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await enterLobby(p1, true);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p1.getNotifications().allowLobbyParticipant();
|
||||
|
||||
const notificationText = await p2.getNotifications().getLobbyParticipantAccessGranted();
|
||||
|
||||
expect(notificationText.includes(P1_DISPLAY_NAME)).toBe(true);
|
||||
expect(notificationText.includes(P3_DISPLAY_NAME)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyParticipantAccessGranted();
|
||||
|
||||
// ensure 3 participants in the call will check for the third one that muc is joined, ice connected,
|
||||
// media is being receiving and there are two remote streams
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());
|
||||
|
||||
expect(name).toBe(P3_DISPLAY_NAME);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('entering in lobby and deny', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// the first time tests is executed we need to enter display name,
|
||||
// for next execution that will be locally stored
|
||||
await enterLobby(p1, false);
|
||||
|
||||
// moderator rejects access
|
||||
await p1.getNotifications().rejectLobbyParticipant();
|
||||
|
||||
// deny notification on 2nd participant
|
||||
const notificationText = await p2.getNotifications().getLobbyParticipantAccessDenied();
|
||||
|
||||
expect(notificationText.includes(P1_DISPLAY_NAME)).toBe(true);
|
||||
expect(notificationText.includes(P3_DISPLAY_NAME)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyParticipantAccessDenied();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
|
||||
it('approve from participants pane', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, false);
|
||||
|
||||
// moderator allows access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.admitLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// ensure 3 participants in the call will check for the third one that muc is joined, ice connected,
|
||||
// media is being receiving and there are two remote streams
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
// now check third one display name in the room, is the one set in the prejoin screen
|
||||
const name = await p1.getFilmstrip().getRemoteDisplayName(await p3.getEndpointId());
|
||||
|
||||
expect(name).toBe(P3_DISPLAY_NAME);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('reject from participants pane', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, false);
|
||||
|
||||
// moderator rejects access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.rejectLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
// The third participant should see a warning that his access to the room was denied
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
// check Lobby room not left
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('lobby user leave', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
await ctx.p3.hangup();
|
||||
|
||||
// check that moderator (participant 1) no longer sees notification about participant in lobby
|
||||
await p1.getNotifications().waitForHideOfKnockingParticipants();
|
||||
});
|
||||
|
||||
it('conference ended in lobby', async () => {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
await p1.hangup();
|
||||
await p2.hangup();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.driver.$('.dialog.leaveReason').isExisting();
|
||||
|
||||
await p3.driver.waitUntil(
|
||||
async () => !await p3.getLobbyScreen().isLobbyRoomJoined(),
|
||||
{
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'p2 did not leave lobby'
|
||||
}
|
||||
);
|
||||
|
||||
await p3.hangup();
|
||||
});
|
||||
|
||||
it('disable while participant in lobby', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enableLobby();
|
||||
await enterLobby(p1);
|
||||
|
||||
// WebParticipant participant1 = getParticipant1();
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
await p1SecurityDialog.toggleLobby();
|
||||
await p1SecurityDialog.waitForLobbyEnabled();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
await p3.waitToJoinMUC();
|
||||
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
});
|
||||
|
||||
it('change of moderators in lobby', async () => {
|
||||
await Promise.all([ ctx.p1.hangup(), ctx.p2.hangup(), ctx.p3.hangup() ]);
|
||||
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
// hanging up the first one, which is moderator and second one should be
|
||||
await p1.hangup();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
() => p2.isModerator(),
|
||||
{
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'p2 is not moderator after p1 leaves'
|
||||
}
|
||||
);
|
||||
|
||||
const p2SecurityDialog = p2.getSecurityDialog();
|
||||
|
||||
await p2.getToolbar().clickSecurityButton();
|
||||
await p2SecurityDialog.waitForDisplay();
|
||||
|
||||
await p2SecurityDialog.toggleLobby();
|
||||
await p2SecurityDialog.waitForLobbyEnabled();
|
||||
|
||||
// here the important check is whether the moderator sees the knocking participant
|
||||
await enterLobby(p2, false);
|
||||
|
||||
await Promise.all([ ctx.p1.hangup(), ctx.p2.hangup(), ctx.p3.hangup() ]);
|
||||
});
|
||||
|
||||
it('shared password', async () => {
|
||||
await ensureTwoParticipants(ctx);
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
await enableLobby();
|
||||
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
expect(await p1SecurityDialog.isLocked()).toBe(false);
|
||||
|
||||
const roomPasscode = String(Math.random() * 1_000);
|
||||
|
||||
await p1SecurityDialog.addPassword(roomPasscode);
|
||||
|
||||
await p1.driver.waitUntil(
|
||||
() => p1SecurityDialog.isLocked(),
|
||||
{
|
||||
timeout: 2000,
|
||||
timeoutMsg: 'room did not lock for p1'
|
||||
}
|
||||
);
|
||||
|
||||
await enterLobby(p1, false);
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// now fill in password
|
||||
const lobbyScreen = p3.getLobbyScreen();
|
||||
|
||||
await lobbyScreen.enterPassword(roomPasscode);
|
||||
|
||||
await p3.waitToJoinMUC();
|
||||
await p3.waitForIceConnected();
|
||||
await p3.waitForSendReceiveData();
|
||||
});
|
||||
|
||||
it('enable with more than two participants', async () => {
|
||||
await Promise.all([ ctx.p1.hangup(), ctx.p2.hangup(), ctx.p3.hangup() ]);
|
||||
|
||||
|
||||
await ensureThreeParticipants(ctx);
|
||||
|
||||
await enableLobby();
|
||||
|
||||
// we need to check remote participants as isInMuc has not changed its value as
|
||||
// the bug is triggered by presence with status 322 which is not handled correctly
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p1.waitForRemoteStreams(2);
|
||||
await p2.waitForRemoteStreams(2);
|
||||
await p3.waitForRemoteStreams(2);
|
||||
});
|
||||
|
||||
it('moderator leaves while lobby enabled', async () => {
|
||||
const { p1, p2, p3 } = ctx;
|
||||
|
||||
await p3.hangup();
|
||||
await p1.hangup();
|
||||
|
||||
await p2.driver.waitUntil(
|
||||
() => p2.isModerator(),
|
||||
{
|
||||
timeout: 3000,
|
||||
timeoutMsg: 'p2 is not moderator after p1 leaves'
|
||||
}
|
||||
);
|
||||
|
||||
const lobbyScreen = p2.getLobbyScreen();
|
||||
|
||||
expect(await lobbyScreen.isLobbyRoomJoined()).toBe(true);
|
||||
});
|
||||
|
||||
it('reject and approve in pre-join', async () => {
|
||||
await ctx.p2.hangup();
|
||||
|
||||
await ensureTwoParticipants(ctx);
|
||||
await enableLobby();
|
||||
|
||||
const { p1 } = ctx;
|
||||
|
||||
const knockingParticipant = await enterLobby(p1, true, true);
|
||||
|
||||
// moderator rejects access
|
||||
const p1ParticipantsPane = p1.getParticipantsPane();
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.rejectLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
const { p3 } = ctx;
|
||||
|
||||
// check the denied one is out of lobby, sees the notification about it
|
||||
// The third participant should see a warning that his access to the room was denied
|
||||
await p3.getNotifications().waitForLobbyAccessDeniedNotification();
|
||||
|
||||
// check Lobby room left
|
||||
expect(await p3.getLobbyScreen().isLobbyRoomJoined()).toBe(false);
|
||||
|
||||
// try again entering the lobby with the third one and approve it
|
||||
// check that everything is fine in the meeting
|
||||
await p3.getNotifications().closeLocalLobbyAccessDenied();
|
||||
|
||||
// let's retry to enter the lobby and approve this time
|
||||
const lobbyScreen = p3.getPreJoinScreen();
|
||||
|
||||
// click join button
|
||||
await lobbyScreen.getJoinButton().click();
|
||||
await lobbyScreen.waitToJoinLobby();
|
||||
|
||||
// check that moderator (participant 1) sees notification about participant in lobby
|
||||
const name = await p1.getNotifications().getKnockingParticipantName();
|
||||
|
||||
expect(name).toBe(P3_DISPLAY_NAME);
|
||||
expect(await lobbyScreen.isLobbyRoomJoined()).toBe(true);
|
||||
|
||||
await p1ParticipantsPane.open();
|
||||
await p1ParticipantsPane.admitLobbyParticipant(knockingParticipant);
|
||||
await p1ParticipantsPane.close();
|
||||
|
||||
await p3.waitForParticipants(2);
|
||||
await p3.waitForRemoteStreams(2);
|
||||
|
||||
expect(await p3.getFilmstrip().countVisibleThumbnails()).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Enable lobby and check that it is enabled.
|
||||
*/
|
||||
async function enableLobby() {
|
||||
const { p1, p2 } = ctx;
|
||||
|
||||
const p1SecurityDialog = p1.getSecurityDialog();
|
||||
|
||||
await p1.getToolbar().clickSecurityButton();
|
||||
await p1SecurityDialog.waitForDisplay();
|
||||
|
||||
expect(await p1SecurityDialog.isLobbyEnabled()).toBe(false);
|
||||
|
||||
await p1SecurityDialog.toggleLobby();
|
||||
await p1SecurityDialog.waitForLobbyEnabled();
|
||||
|
||||
expect((await p2.getNotifications().getLobbyEnabledText()).includes(p1.displayName)).toBe(true);
|
||||
|
||||
await p2.getNotifications().closeLobbyEnabled();
|
||||
|
||||
const p2SecurityDialog = p2.getSecurityDialog();
|
||||
|
||||
await p2.getToolbar().clickSecurityButton();
|
||||
await p2SecurityDialog.waitForDisplay();
|
||||
|
||||
// lobby is visible to moderators only, this depends on whether deployment is all moderators or not
|
||||
if (await p2.isModerator()) {
|
||||
await p2SecurityDialog.waitForLobbyEnabled();
|
||||
} else {
|
||||
expect(await p2SecurityDialog.isLobbySectionPresent()).toBe(false);
|
||||
}
|
||||
|
||||
// let's close the security dialog, or we will not be able to click
|
||||
// on popups for allow/deny participants
|
||||
await p1SecurityDialog.clickCloseButton();
|
||||
await p2SecurityDialog.clickCloseButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects that lobby is enabled for the room we will try to join.
|
||||
* Lobby UI is shown, enter display name and join.
|
||||
* Checks Lobby UI and also that when joining the moderator sees corresponding notifications.
|
||||
*
|
||||
* @param participant The participant that is moderator in the meeting.
|
||||
* @param enterDisplayName whether to enter display name. We need to enter display name only the first time when
|
||||
* a participant sees the lobby screen, next time visiting the page display name will be pre-filled
|
||||
* from local storage.
|
||||
* @param usePreJoin
|
||||
* @return the participant name knocking.
|
||||
*/
|
||||
async function enterLobby(participant: Participant, enterDisplayName = false, usePreJoin = false) {
|
||||
const options: IJoinOptions = {};
|
||||
|
||||
if (usePreJoin) {
|
||||
options.configOverwrite = {
|
||||
prejoinConfig: {
|
||||
enabled: true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
await ensureThreeParticipants(ctx, {
|
||||
...options,
|
||||
skipDisplayName: true,
|
||||
skipWaitToJoin: true,
|
||||
skipInMeetingChecks: true
|
||||
});
|
||||
|
||||
const { p3 } = ctx;
|
||||
let screen: PreMeetingScreen;
|
||||
|
||||
// WebParticipant participant3 = getParticipant3();
|
||||
// ParentPreMeetingScreen lobbyScreen;
|
||||
if (usePreJoin) {
|
||||
screen = p3.getPreJoinScreen();
|
||||
} else {
|
||||
screen = p3.getLobbyScreen();
|
||||
}
|
||||
|
||||
// participant 3 should be now on pre-join screen
|
||||
await screen.waitForLoading();
|
||||
|
||||
const displayNameInput = screen.getDisplayNameInput();
|
||||
|
||||
// check display name is visible
|
||||
expect(await displayNameInput.isExisting()).toBe(true);
|
||||
expect(await displayNameInput.isDisplayed()).toBe(true);
|
||||
|
||||
const joinButton = screen.getJoinButton();
|
||||
|
||||
expect(await joinButton.isExisting()).toBe(true);
|
||||
|
||||
if (enterDisplayName) {
|
||||
let classes = await joinButton.getAttribute('class');
|
||||
|
||||
if (!usePreJoin) {
|
||||
// check join button is disabled
|
||||
expect(classes.includes('disabled')).toBe(true);
|
||||
}
|
||||
|
||||
// TODO check that password is hidden as the room does not have password
|
||||
// this check needs to be added once the functionality exists
|
||||
|
||||
// enter display name
|
||||
await screen.enterDisplayName(P3_DISPLAY_NAME);
|
||||
|
||||
// check join button is enabled
|
||||
classes = await joinButton.getAttribute('class');
|
||||
expect(classes.includes('disabled')).toBe(false);
|
||||
}
|
||||
|
||||
// click join button
|
||||
await screen.getJoinButton().click();
|
||||
await screen.waitToJoinLobby();
|
||||
|
||||
// check no join button
|
||||
expect(!await joinButton.isExisting() || !await joinButton.isDisplayed() || !await joinButton.isEnabled())
|
||||
.toBe(true);
|
||||
|
||||
// new screen, is password button shown
|
||||
const passwordButton = screen.getPasswordButton();
|
||||
|
||||
expect(await passwordButton.isExisting()).toBe(true);
|
||||
expect(await passwordButton.isEnabled()).toBe(true);
|
||||
|
||||
// check that moderator (participant 1) sees notification about participant in lobby
|
||||
const name = await participant.getNotifications().getKnockingParticipantName();
|
||||
|
||||
expect(name).toBe(P3_DISPLAY_NAME);
|
||||
expect(await screen.isLobbyRoomJoined()).toBe(true);
|
||||
|
||||
return name;
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ensureTwoParticipants
|
||||
} from '../../helpers/participants';
|
||||
|
||||
describe('Desktop sharing - ', () => {
|
||||
describe('Desktop sharing', () => {
|
||||
it('start', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
configOverwrite: {
|
||||
@@ -203,8 +203,7 @@ describe('Desktop sharing - ', () => {
|
||||
await checkForScreensharingTile(p3, p2);
|
||||
|
||||
// the video should be playing
|
||||
await p1.driver.waitUntil(async () =>
|
||||
await p1.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
await p1.driver.waitUntil(() => p1.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'expected remote screen share to be on large'
|
||||
});
|
||||
@@ -255,8 +254,7 @@ describe('Desktop sharing - ', () => {
|
||||
expect(await p1.driver.execute(() => APP.UI.getLargeVideoID())).toBe(`${await p3.getEndpointId()}-v1`);
|
||||
|
||||
// the video should be playing
|
||||
await p1.driver.waitUntil(async () =>
|
||||
await p1.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
await p1.driver.waitUntil(() => p1.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived()), {
|
||||
timeout: 5_000,
|
||||
timeoutMsg: 'expected remote screen share to be on large'
|
||||
});
|
||||
@@ -296,49 +294,20 @@ describe('Desktop sharing - ', () => {
|
||||
expect(await p4.driver.execute(() => JitsiMeetJS.app.testing.isLargeVideoReceived())).toBe(true);
|
||||
|
||||
const p1EndpointId = await p1.getEndpointId();
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
// p4 has lastN=2 and has selected p3. With p2 being dominant speaker p4 should eventually
|
||||
// see video for [p3, p2] and p1 as ninja.
|
||||
await p4.driver.$(`//span[@id='participant_${p1EndpointId}']//span[@class='connection_ninja']`)
|
||||
.waitForDisplayed({
|
||||
timeout: 15_000
|
||||
});
|
||||
|
||||
const p2EndpointId = await p2.getEndpointId();
|
||||
|
||||
await p4.driver.waitUntil(async () =>
|
||||
await p4.driver.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
p2EndpointId) && await p4.driver.$(
|
||||
`//span[@id="participant_${p2EndpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: 'expected remote video to be received 15s'
|
||||
});
|
||||
|
||||
await p4.driver.waitUntil(async () =>
|
||||
await p4.driver.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
p2EndpointId) && await p4.driver.$(
|
||||
`//span[@id="participant_${p2EndpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: 'expected remote video to be received 15s'
|
||||
});
|
||||
await p4.waitForNinjaIcon(p1EndpointId);
|
||||
await p4.waitForRemoteVideo(p2EndpointId);
|
||||
|
||||
// Let's switch and check, muting participant 2 and unmuting 1 will leave participant 1 as dominant
|
||||
await p1.getToolbar().clickAudioUnmuteButton();
|
||||
await p2.getToolbar().clickAudioMuteButton();
|
||||
|
||||
// Participant4 should eventually see video for [p3, p1] and p2 as a ninja.
|
||||
await p4.driver.$(`//span[@id='participant_${p2EndpointId}']//span[@class='connection_ninja']`)
|
||||
.waitForDisplayed({
|
||||
timeout: 15_000
|
||||
});
|
||||
|
||||
await p4.driver.waitUntil(async () =>
|
||||
await p4.driver.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
p1EndpointId) && await p4.driver.$(
|
||||
`//span[@id="participant_${p1EndpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: 'expected remote video to be received 15s'
|
||||
});
|
||||
await p4.waitForNinjaIcon(p2EndpointId);
|
||||
await p4.waitForRemoteVideo(p1EndpointId);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
73
tests/specs/4way/lastN.spec.ts
Normal file
73
tests/specs/4way/lastN.spec.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { ensureFourParticipants, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
|
||||
|
||||
describe('lastN', () => {
|
||||
it('joining the meeting', async () => {
|
||||
await ensureTwoParticipants(ctx, {
|
||||
skipInMeetingChecks: true,
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: true,
|
||||
startWithVideoMuted: true,
|
||||
channelLastN: 1
|
||||
}
|
||||
});
|
||||
|
||||
await ensureThreeParticipants(ctx, {
|
||||
skipInMeetingChecks: true,
|
||||
configOverwrite: {
|
||||
channelLastN: 1
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('checks', async () => {
|
||||
const { p3 } = ctx;
|
||||
const p3Toolbar = p3.getToolbar();
|
||||
|
||||
await p3.waitForSendData();
|
||||
|
||||
await ctx.p1.waitForRemoteVideo(await p3.getEndpointId());
|
||||
|
||||
// Mute audio on participant3.
|
||||
await p3Toolbar.clickAudioMuteButton();
|
||||
|
||||
await ensureFourParticipants(ctx, {
|
||||
skipInMeetingChecks: true,
|
||||
configOverwrite: {
|
||||
channelLastN: 1
|
||||
}
|
||||
});
|
||||
|
||||
const { p1, p2, p4 } = ctx;
|
||||
|
||||
await p4.waitForSendReceiveData();
|
||||
|
||||
// Mute audio on p4 and unmute p3.
|
||||
await p4.getToolbar().clickAudioMuteButton();
|
||||
await p3Toolbar.clickAudioUnmuteButton();
|
||||
|
||||
const p4EndpointId = await p4.getEndpointId();
|
||||
const p3EndpointId = await p3.getEndpointId();
|
||||
|
||||
// Check if p1 starts receiving video from p3 and p4 shows up as ninja.
|
||||
await p1.waitForNinjaIcon(p4EndpointId);
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
|
||||
// At this point, mute video of p3 and others should be receiving p4's video.
|
||||
// Mute p1's video
|
||||
await p3Toolbar.clickVideoMuteButton();
|
||||
await p3.getParticipantsPane().assertVideoMuteIconIsDisplayed(p3);
|
||||
await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p3);
|
||||
await p1.waitForRemoteVideo(p4EndpointId);
|
||||
|
||||
// Unmute p3's video and others should switch to receiving p3's video.
|
||||
await p3Toolbar.clickVideoUnmuteButton();
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
await p1.waitForNinjaIcon(p4EndpointId);
|
||||
|
||||
// Mute p3's audio and unmute p2's audio. Other endpoints should continue to receive video from p3
|
||||
// even though p2 is the dominant speaker.
|
||||
await p3Toolbar.clickAudioMuteButton();
|
||||
await p2.getToolbar().clickAudioUnmuteButton();
|
||||
await p1.waitForRemoteVideo(p3EndpointId);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
|
||||
describe('Chat Panel - ', () => {
|
||||
it('join participant', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
});
|
||||
describe('Chat Panel', () => {
|
||||
it('join participant', () => ensureOneParticipant(ctx));
|
||||
|
||||
it('start closed', async () => {
|
||||
expect(await ctx.p1.getChatPanel().isOpen()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import process from 'node:process';
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
import { cleanup, isDialInEnabled, waitForAudioFromDialInParticipant } from '../helpers/DialIn';
|
||||
|
||||
describe('Dial-In - ', () => {
|
||||
describe('Dial-In', () => {
|
||||
it('join participant', async () => {
|
||||
// check rest url is configured
|
||||
if (!process.env.DIAL_IN_REST_URL) {
|
||||
@@ -30,7 +30,7 @@ describe('Dial-In - ', () => {
|
||||
console.error('dial-in.test.no-pin');
|
||||
}
|
||||
|
||||
expect(dialInPin.length >= 9).toBe(true);
|
||||
expect(dialInPin.length >= 8).toBe(true);
|
||||
|
||||
ctx.dialInPin = dialInPin;
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { ensureOneParticipant } from '../../helpers/participants';
|
||||
import { isDialInEnabled } from '../helpers/DialIn';
|
||||
|
||||
describe('Invite - ', () => {
|
||||
it('join participant', async () => {
|
||||
await ensureOneParticipant(ctx);
|
||||
});
|
||||
describe('Invite', () => {
|
||||
it('join participant', () => ensureOneParticipant(ctx));
|
||||
|
||||
it('url displayed', async () => {
|
||||
const { p1 } = ctx;
|
||||
|
||||
Reference in New Issue
Block a user