|
@@ -1,6 +1,7 @@
|
1
|
1
|
/* global APP $ */
|
2
|
2
|
|
3
|
3
|
import { multiremotebrowser } from '@wdio/globals';
|
|
4
|
+import assert from 'assert';
|
4
|
5
|
import { Key } from 'webdriverio';
|
5
|
6
|
|
6
|
7
|
import { IConfig } from '../../react/features/base/config/configType';
|
|
@@ -10,6 +11,7 @@ import ChatPanel from '../pageobjects/ChatPanel';
|
10
|
11
|
import Filmstrip from '../pageobjects/Filmstrip';
|
11
|
12
|
import IframeAPI from '../pageobjects/IframeAPI';
|
12
|
13
|
import InviteDialog from '../pageobjects/InviteDialog';
|
|
14
|
+import LargeVideo from '../pageobjects/LargeVideo';
|
13
|
15
|
import LobbyScreen from '../pageobjects/LobbyScreen';
|
14
|
16
|
import Notifications from '../pageobjects/Notifications';
|
15
|
17
|
import ParticipantsPane from '../pageobjects/ParticipantsPane';
|
|
@@ -27,6 +29,13 @@ export const P2_DISPLAY_NAME = 'p2';
|
27
|
29
|
export const P3_DISPLAY_NAME = 'p3';
|
28
|
30
|
export const P4_DISPLAY_NAME = 'p4';
|
29
|
31
|
|
|
32
|
+interface IWaitForSendReceiveDataOptions {
|
|
33
|
+ checkReceive?: boolean;
|
|
34
|
+ checkSend?: boolean;
|
|
35
|
+ msg?: string;
|
|
36
|
+ timeout?: number;
|
|
37
|
+}
|
|
38
|
+
|
30
|
39
|
/**
|
31
|
40
|
* Participant.
|
32
|
41
|
*/
|
|
@@ -91,7 +100,7 @@ export class Participant {
|
91
|
100
|
async getEndpointId(): Promise<string> {
|
92
|
101
|
if (!this._endpointId) {
|
93
|
102
|
this._endpointId = await this.driver.execute(() => { // eslint-disable-line arrow-body-style
|
94
|
|
- return APP.conference.getMyUserId();
|
|
103
|
+ return APP?.conference?.getMyUserId();
|
95
|
104
|
});
|
96
|
105
|
}
|
97
|
106
|
|
|
@@ -209,7 +218,7 @@ export class Participant {
|
209
|
218
|
const parallel = [];
|
210
|
219
|
|
211
|
220
|
parallel.push(driver.execute((name, sessionId, prefix) => {
|
212
|
|
- APP.UI.dockToolbar(true);
|
|
221
|
+ APP?.UI?.dockToolbar(true);
|
213
|
222
|
|
214
|
223
|
// disable keyframe animations (.fadeIn and .fadeOut classes)
|
215
|
224
|
$('<style>.notransition * { '
|
|
@@ -274,8 +283,8 @@ export class Participant {
|
274
|
283
|
/**
|
275
|
284
|
* Checks if the participant is in the meeting.
|
276
|
285
|
*/
|
277
|
|
- async isInMuc() {
|
278
|
|
- return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
|
|
286
|
+ isInMuc() {
|
|
287
|
+ return this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
|
279
|
288
|
}
|
280
|
289
|
|
281
|
290
|
/**
|
|
@@ -326,7 +335,7 @@ export class Participant {
|
326
|
335
|
const driver = this.driver;
|
327
|
336
|
|
328
|
337
|
return driver.waitUntil(() =>
|
329
|
|
- driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
|
338
|
+ driver.execute(() => APP?.conference?.getConnectionState() === 'connected'), {
|
330
|
339
|
timeout: 15_000,
|
331
|
340
|
timeoutMsg: `expected ICE to be connected for 15s for ${this.name}`
|
332
|
341
|
});
|
|
@@ -335,47 +344,35 @@ export class Participant {
|
335
|
344
|
/**
|
336
|
345
|
* Waits for send and receive data.
|
337
|
346
|
*
|
|
347
|
+ * @param {Object} options
|
|
348
|
+ * @param {boolean} options.checkSend - If true we will chec
|
338
|
349
|
* @returns {Promise<void>}
|
339
|
350
|
*/
|
340
|
|
- async waitForSendReceiveData(
|
341
|
|
- timeout = 15_000, msg = `expected to receive/send data in 15s for ${this.name}`): Promise<void> {
|
342
|
|
- const driver = this.driver;
|
|
351
|
+ waitForSendReceiveData({
|
|
352
|
+ checkSend = true,
|
|
353
|
+ checkReceive = true,
|
|
354
|
+ timeout = 15_000,
|
|
355
|
+ msg
|
|
356
|
+ } = {} as IWaitForSendReceiveDataOptions): Promise<void> {
|
|
357
|
+ if (!checkSend && !checkReceive) {
|
|
358
|
+ return Promise.resolve();
|
|
359
|
+ }
|
|
360
|
+
|
|
361
|
+ const lMsg = msg ?? `expected to ${
|
|
362
|
+ checkSend && checkReceive ? 'receive/send' : checkSend ? 'send' : 'receive'} data in 15s for ${this.name}`;
|
343
|
363
|
|
344
|
|
- return driver.waitUntil(() => driver.execute(() => {
|
345
|
|
- const stats = APP.conference.getStats();
|
|
364
|
+ return this.driver.waitUntil(() => this.driver.execute((pCheckSend: boolean, pCheckReceive: boolean) => {
|
|
365
|
+ const stats = APP?.conference?.getStats();
|
346
|
366
|
const bitrateMap = stats?.bitrate || {};
|
347
|
367
|
const rtpStats = {
|
348
|
368
|
uploadBitrate: bitrateMap.upload || 0,
|
349
|
369
|
downloadBitrate: bitrateMap.download || 0
|
350
|
370
|
};
|
351
|
371
|
|
352
|
|
- return rtpStats.uploadBitrate > 0 && rtpStats.downloadBitrate > 0;
|
353
|
|
- }), {
|
|
372
|
+ return (rtpStats.uploadBitrate > 0 || !pCheckSend) && (rtpStats.downloadBitrate > 0 || !pCheckReceive);
|
|
373
|
+ }, checkSend, checkReceive), {
|
354
|
374
|
timeout,
|
355
|
|
- timeoutMsg: msg
|
356
|
|
- });
|
357
|
|
- }
|
358
|
|
-
|
359
|
|
- /**
|
360
|
|
- * Waits for send and receive data.
|
361
|
|
- *
|
362
|
|
- * @returns {Promise<void>}
|
363
|
|
- */
|
364
|
|
- async waitForSendData(
|
365
|
|
- timeout = 15_000, msg = `expected to send data in 15s for ${this.name}`): Promise<void> {
|
366
|
|
- const driver = this.driver;
|
367
|
|
-
|
368
|
|
- return driver.waitUntil(() => driver.execute(() => {
|
369
|
|
- const stats = APP.conference.getStats();
|
370
|
|
- const bitrateMap = stats?.bitrate || {};
|
371
|
|
- const rtpStats = {
|
372
|
|
- uploadBitrate: bitrateMap.upload || 0
|
373
|
|
- };
|
374
|
|
-
|
375
|
|
- return rtpStats.uploadBitrate > 0;
|
376
|
|
- }), {
|
377
|
|
- timeout,
|
378
|
|
- timeoutMsg: msg
|
|
375
|
+ timeoutMsg: lMsg
|
379
|
376
|
});
|
380
|
377
|
}
|
381
|
378
|
|
|
@@ -389,7 +386,7 @@ export class Participant {
|
389
|
386
|
const driver = this.driver;
|
390
|
387
|
|
391
|
388
|
return driver.waitUntil(() =>
|
392
|
|
- driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
|
389
|
+ driver.execute(count => (APP?.conference?.getNumberOfParticipantsWithTracks() ?? -1) >= count, number), {
|
393
|
390
|
timeout: 15_000,
|
394
|
391
|
timeoutMsg: `expected number of remote streams:${number} in 15s for ${this.name}`
|
395
|
392
|
});
|
|
@@ -405,10 +402,12 @@ export class Participant {
|
405
|
402
|
waitForParticipants(number: number, msg?: string): Promise<void> {
|
406
|
403
|
const driver = this.driver;
|
407
|
404
|
|
408
|
|
- return driver.waitUntil(() => driver.execute(count => APP.conference.listMembers().length === count, number), {
|
409
|
|
- timeout: 15_000,
|
410
|
|
- timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
|
411
|
|
- });
|
|
405
|
+ return driver.waitUntil(
|
|
406
|
+ () => driver.execute(count => (APP?.conference?.listMembers()?.length ?? -1) === count, number),
|
|
407
|
+ {
|
|
408
|
+ timeout: 15_000,
|
|
409
|
+ timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
|
|
410
|
+ });
|
412
|
411
|
}
|
413
|
412
|
|
414
|
413
|
/**
|
|
@@ -470,6 +469,15 @@ export class Participant {
|
470
|
469
|
return new ParticipantsPane(this);
|
471
|
470
|
}
|
472
|
471
|
|
|
472
|
+ /**
|
|
473
|
+ * Returns the large video page object.
|
|
474
|
+ *
|
|
475
|
+ * @returns {LargeVideo}
|
|
476
|
+ */
|
|
477
|
+ getLargeVideo(): LargeVideo {
|
|
478
|
+ return new LargeVideo(this);
|
|
479
|
+ }
|
|
480
|
+
|
473
|
481
|
/**
|
474
|
482
|
* Returns the videoQuality Dialog.
|
475
|
483
|
*
|
|
@@ -546,7 +554,7 @@ export class Participant {
|
546
|
554
|
}
|
547
|
555
|
|
548
|
556
|
// do a hangup, to make sure unavailable presence is sent
|
549
|
|
- await this.driver.execute(() => typeof APP !== 'undefined' && APP?.conference?.hangup());
|
|
557
|
+ await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.hangup());
|
550
|
558
|
|
551
|
559
|
// let's give it some time to leave the muc, we redirect after hangup so we should wait for the
|
552
|
560
|
// change of url
|
|
@@ -608,29 +616,6 @@ export class Participant {
|
608
|
616
|
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
609
|
617
|
}
|
610
|
618
|
|
611
|
|
- /**
|
612
|
|
- * Gets avatar SRC attribute for the one displayed on large video.
|
613
|
|
- */
|
614
|
|
- async getLargeVideoAvatar() {
|
615
|
|
- const avatar = this.driver.$('//img[@id="dominantSpeakerAvatar"]');
|
616
|
|
-
|
617
|
|
- return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
618
|
|
- }
|
619
|
|
-
|
620
|
|
- /**
|
621
|
|
- * Returns resource part of the JID of the user who is currently displayed in the large video area.
|
622
|
|
- */
|
623
|
|
- async getLargeVideoResource() {
|
624
|
|
- return await this.driver.execute(() => APP.UI.getLargeVideoID());
|
625
|
|
- }
|
626
|
|
-
|
627
|
|
- /**
|
628
|
|
- * Returns the source of the large video currently shown.
|
629
|
|
- */
|
630
|
|
- async getLargeVideoId() {
|
631
|
|
- return this.driver.execute('return document.getElementById("largeVideo").srcObject.id');
|
632
|
|
- }
|
633
|
|
-
|
634
|
619
|
/**
|
635
|
620
|
* Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
|
636
|
621
|
* There are 3 options for avatar:
|
|
@@ -700,6 +685,85 @@ export class Participant {
|
700
|
685
|
return this.driver.$('div[data-testid="dialog.leaveReason"]').isDisplayed();
|
701
|
686
|
}
|
702
|
687
|
|
|
688
|
+ /**
|
|
689
|
+ * Returns the audio level for a participant.
|
|
690
|
+ *
|
|
691
|
+ * @param observer
|
|
692
|
+ * @param participant
|
|
693
|
+ * @return
|
|
694
|
+ */
|
|
695
|
+ async getRemoteAudioLevel(p: Participant) {
|
|
696
|
+ const jid = await p.getEndpointId();
|
|
697
|
+
|
|
698
|
+ return await this.driver.execute(id => {
|
|
699
|
+ const level = APP?.conference?.getPeerSSRCAudioLevel(id);
|
|
700
|
+
|
|
701
|
+ return level ? level.toFixed(2) : null;
|
|
702
|
+ }, jid);
|
|
703
|
+ }
|
|
704
|
+
|
|
705
|
+ /**
|
|
706
|
+ * For the participant to have his audio muted/unmuted from given observer's
|
|
707
|
+ * perspective. The method will fail the test if something goes wrong or
|
|
708
|
+ * the audio muted status is different than the expected one. We wait up to
|
|
709
|
+ * 3 seconds for the expected status to appear.
|
|
710
|
+ *
|
|
711
|
+ * @param testee - instance of the participant for whom we're checking the audio muted status.
|
|
712
|
+ * @param muted - <tt>true</tt> to wait for audio muted status or <tt>false</tt> to wait for the participant to
|
|
713
|
+ * unmute.
|
|
714
|
+ */
|
|
715
|
+ async waitForAudioMuted(testee: Participant, muted: boolean): Promise<void> {
|
|
716
|
+ // Waits for the correct icon
|
|
717
|
+ await this.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, !muted);
|
|
718
|
+
|
|
719
|
+ // Extended timeout for 'unmuted' to make tests more resilient to
|
|
720
|
+ // unexpected glitches.
|
|
721
|
+ const timeout = muted ? 3_000 : 6_000;
|
|
722
|
+
|
|
723
|
+ // Give it 3 seconds to not get any audio or to receive some
|
|
724
|
+ // depending on "muted" argument
|
|
725
|
+ try {
|
|
726
|
+ await this.driver.waitUntil(async () => {
|
|
727
|
+ const audioLevel = await this.getRemoteAudioLevel(testee);
|
|
728
|
+
|
|
729
|
+ if (muted) {
|
|
730
|
+ if (audioLevel !== null && audioLevel > 0.1) {
|
|
731
|
+ console.log(`muted exiting on: ${audioLevel}`);
|
|
732
|
+
|
|
733
|
+ return true;
|
|
734
|
+ }
|
|
735
|
+
|
|
736
|
+ return false;
|
|
737
|
+ }
|
|
738
|
+
|
|
739
|
+ // When testing for unmuted we wait for first sound
|
|
740
|
+ if (audioLevel !== null && audioLevel > 0.1) {
|
|
741
|
+ console.log(`unmuted exiting on: ${audioLevel}`);
|
|
742
|
+
|
|
743
|
+ return true;
|
|
744
|
+ }
|
|
745
|
+
|
|
746
|
+ return false;
|
|
747
|
+ },
|
|
748
|
+ { timeout });
|
|
749
|
+
|
|
750
|
+ // When testing for muted we don't want to have
|
|
751
|
+ // the condition succeeded
|
|
752
|
+ if (muted) {
|
|
753
|
+ const name = await testee.displayName;
|
|
754
|
+
|
|
755
|
+ assert.fail(`There was some sound coming from muted: '${name}'`);
|
|
756
|
+ } // else we're good for unmuted participant
|
|
757
|
+ } catch (_timeoutE) {
|
|
758
|
+ if (!muted) {
|
|
759
|
+ const name = await testee.displayName;
|
|
760
|
+
|
|
761
|
+ assert.fail(`There was no sound from unmuted: '${name}'`);
|
|
762
|
+ } // else we're good for muted participant
|
|
763
|
+ }
|
|
764
|
+ }
|
|
765
|
+
|
|
766
|
+
|
703
|
767
|
/**
|
704
|
768
|
* Waits for remote video state - receiving and displayed.
|
705
|
769
|
* @param endpointId
|