Selaa lähdekoodia

feat(tests): Adds self view, display name and end conference tests. (#15432)

* feat(tests): Use shorter display names for screenshots.

* feat(tests): Adds self view tests.

* feat(tests): Adds display name test.

* feat(tests): Adds end conference test.
factor2
Дамян Минков 5 kuukautta sitten
vanhempi
commit
78b17c8d17
No account linked to committer's email address

+ 30
- 5
tests/helpers/Participant.ts Näytä tiedosto

@@ -1,6 +1,7 @@
1 1
 /* global APP $ */
2 2
 
3 3
 import { multiremotebrowser } from '@wdio/globals';
4
+import { Key } from 'webdriverio';
4 5
 
5 6
 import { IConfig } from '../../react/features/base/config/configType';
6 7
 import { urlObjectToString } from '../../react/features/base/util/uri';
@@ -134,7 +135,7 @@ export class Participant {
134 135
         if (!options.skipDisplayName) {
135 136
             // @ts-ignore
136 137
             config.userInfo = {
137
-                displayName: this._name
138
+                displayName: options.displayName || this._name
138 139
             };
139 140
         }
140 141
 
@@ -441,16 +442,40 @@ export class Participant {
441 442
     }
442 443
 
443 444
     /**
444
-     * Returns the local display name.
445
+     * Returns the local display name element.
446
+     * @private
445 447
      */
446
-    async getLocalDisplayName() {
448
+    private async getLocalDisplayNameElement() {
447 449
         const localVideoContainer = this.driver.$('span[id="localVideoContainer"]');
448 450
 
449 451
         await localVideoContainer.moveTo();
450 452
 
451
-        const localDisplayName = localVideoContainer.$('span[id="localDisplayName"]');
453
+        return localVideoContainer.$('span[id="localDisplayName"]');
454
+    }
455
+
456
+    /**
457
+     * Returns the local display name.
458
+     */
459
+    async getLocalDisplayName() {
460
+        return await (await this.getLocalDisplayNameElement()).getText();
461
+    }
462
+
463
+    /**
464
+     * Sets the display name of the local participant.
465
+     */
466
+    async setLocalDisplayName(displayName: string) {
467
+        const localDisplayName = await this.getLocalDisplayNameElement();
468
+
469
+        await localDisplayName.click();
452 470
 
453
-        return await localDisplayName.getText();
471
+        await this.driver.keys(displayName);
472
+        await this.driver.keys(Key.Return);
473
+
474
+        // just click somewhere to lose focus, to make sure editing has ended
475
+        const localVideoContainer = this.driver.$('span[id="localVideoContainer"]');
476
+
477
+        await localVideoContainer.moveTo();
478
+        await localVideoContainer.click();
454 479
     }
455 480
 
456 481
     /**

+ 62
- 14
tests/helpers/participants.ts Näytä tiedosto

@@ -16,12 +16,7 @@ const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';
16 16
  * @returns {Promise<void>}
17 17
  */
18 18
 export async function ensureOneParticipant(ctx: IContext, options?: IJoinOptions): Promise<void> {
19
-    ctx.p1 = new Participant('participant1');
20
-
21
-    await ctx.p1.joinConference(ctx, {
22
-        ...options,
23
-        skipInMeetingChecks: true
24
-    });
19
+    await joinTheModeratorAsP1(ctx, options);
25 20
 }
26 21
 
27 22
 /**
@@ -31,22 +26,71 @@ export async function ensureOneParticipant(ctx: IContext, options?: IJoinOptions
31 26
  * @param {IJoinOptions} options - The options to use when joining the participant.
32 27
  * @returns {Promise<void>}
33 28
  */
34
-export async function ensureThreeParticipants(ctx: IContext, options?: IJoinOptions): Promise<void> {
29
+export async function ensureThreeParticipants(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
30
+    await joinTheModeratorAsP1(ctx, options);
31
+
32
+    // these need to be all, so we get the error when one fails
33
+    await Promise.all([
34
+        _joinParticipant('participant2', ctx.p2, p => {
35
+            ctx.p2 = p;
36
+        }, {
37
+            displayName: 'p2',
38
+            ...options
39
+        }),
40
+        _joinParticipant('participant3', ctx.p3, p => {
41
+            ctx.p3 = p;
42
+        }, {
43
+            displayName: 'p3',
44
+            ...options
45
+        })
46
+    ]);
47
+
48
+    const { skipInMeetingChecks } = options;
49
+
50
+    await Promise.all([
51
+        skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(2),
52
+        skipInMeetingChecks ? Promise.resolve() : ctx.p3.waitForRemoteStreams(2)
53
+    ]);
54
+}
55
+
56
+/**
57
+ * Ensure that there are four participants.
58
+ *
59
+ * @param {Object} ctx - The context.
60
+ * @param {IJoinOptions} options - The options to use when joining the participant.
61
+ * @returns {Promise<void>}
62
+ */
63
+export async function ensureFourParticipants(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
35 64
     await joinTheModeratorAsP1(ctx, options);
36 65
 
37 66
     // these need to be all, so we get the error when one fails
38 67
     await Promise.all([
39 68
         _joinParticipant('participant2', ctx.p2, p => {
40 69
             ctx.p2 = p;
41
-        }, options),
70
+        }, {
71
+            displayName: 'p2',
72
+            ...options
73
+        }),
42 74
         _joinParticipant('participant3', ctx.p3, p => {
43 75
             ctx.p3 = p;
44
-        }, options)
76
+        }, {
77
+            displayName: 'p3',
78
+            ...options
79
+        }),
80
+        _joinParticipant('participant4', ctx.p4, p => {
81
+            ctx.p4 = p;
82
+        }, {
83
+            displayName: 'p4',
84
+            ...options
85
+        })
45 86
     ]);
46 87
 
88
+    const { skipInMeetingChecks } = options;
89
+
47 90
     await Promise.all([
48
-        ctx.p2.waitForRemoteStreams(2),
49
-        ctx.p3.waitForRemoteStreams(2)
91
+        skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(3),
92
+        skipInMeetingChecks ? Promise.resolve() : ctx.p3.waitForRemoteStreams(3),
93
+        skipInMeetingChecks ? Promise.resolve() : ctx.p3.waitForRemoteStreams(3)
50 94
     ]);
51 95
 }
52 96
 
@@ -58,7 +102,7 @@ export async function ensureThreeParticipants(ctx: IContext, options?: IJoinOpti
58 102
  * @returns {Promise<void>}
59 103
  */
60 104
 async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
61
-    const p1DisplayName = 'participant1';
105
+    const p1DisplayName = 'p1';
62 106
     let token;
63 107
 
64 108
     // if it is jaas create the first one to be moderator and second not moderator
@@ -67,9 +111,10 @@ async function joinTheModeratorAsP1(ctx: IContext, options?: IJoinOptions) {
67 111
     }
68 112
 
69 113
     // make sure the first participant is moderator, if supported by deployment
70
-    await _joinParticipant(p1DisplayName, ctx.p1, p => {
114
+    await _joinParticipant('participant1', ctx.p1, p => {
71 115
         ctx.p1 = p;
72 116
     }, {
117
+        displayName: p1DisplayName,
73 118
         ...options,
74 119
         skipInMeetingChecks: true
75 120
     }, token);
@@ -89,7 +134,10 @@ export async function ensureTwoParticipants(ctx: IContext, options: IJoinOptions
89 134
     await Promise.all([
90 135
         _joinParticipant('participant2', ctx.p2, p => {
91 136
             ctx.p2 = p;
92
-        }, options),
137
+        }, {
138
+            displayName: 'p2',
139
+            ...options
140
+        }),
93 141
         skipInMeetingChecks ? Promise.resolve() : ctx.p1.waitForRemoteStreams(1),
94 142
         skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(1)
95 143
     ]);

+ 5
- 0
tests/helpers/types.ts Näytä tiedosto

@@ -24,6 +24,11 @@ export type IJoinOptions = {
24 24
      */
25 25
     configOverwrite?: IConfig;
26 26
 
27
+    /**
28
+     * The display name to use.
29
+     */
30
+    displayName?: string;
31
+
27 32
     /**
28 33
      * Whether to skip setting display name.
29 34
      */

+ 32
- 0
tests/pageobjects/Filmstrip.ts Näytä tiedosto

@@ -3,6 +3,11 @@ import { Participant } from '../helpers/Participant';
3 3
 import BaseDialog from './BaseDialog';
4 4
 import BasePageObject from './BasePageObject';
5 5
 
6
+const LOCAL_VIDEO_XPATH = '//span[@id="localVideoContainer"]';
7
+const LOCAL_VIDEO_MENU_TRIGGER = '#local-video-menu-trigger';
8
+const LOCAL_USER_CONTROLS = 'aria/Local user controls';
9
+const HIDE_SELF_VIEW_BUTTON_XPATH = '//div[contains(@class, "popover")]//div[@id="hideselfviewButton"]';
10
+
6 11
 /**
7 12
  * Filmstrip elements.
8 13
  */
@@ -121,4 +126,31 @@ export default class Filmstrip extends BasePageObject {
121 126
     async muteVideo(participant: Participant) {
122 127
         await this.clickOnRemoteMenuLink(await participant.getEndpointId(), 'mutevideolink', true);
123 128
     }
129
+
130
+    /**
131
+     * Clicks on the hide self view button from local video.
132
+     */
133
+    async hideSelfView() {
134
+        // open local video menu
135
+        await this.participant.driver.$(LOCAL_VIDEO_MENU_TRIGGER).moveTo();
136
+        await this.participant.driver.$(LOCAL_USER_CONTROLS).moveTo();
137
+
138
+        // click Hide self view button
139
+        const hideSelfViewButton = this.participant.driver.$(HIDE_SELF_VIEW_BUTTON_XPATH);
140
+
141
+        await hideSelfViewButton.waitForExist();
142
+        await hideSelfViewButton.waitForClickable();
143
+        await hideSelfViewButton.click();
144
+    }
145
+
146
+    /**
147
+     * Checks whether the local self view is displayed or not.
148
+     */
149
+    async assertSelfViewIsHidden(hidden: boolean) {
150
+        await this.participant.driver.$(LOCAL_VIDEO_XPATH).waitForDisplayed({
151
+            reverse: hidden,
152
+            timeout: 5000,
153
+            timeoutMsg: `Local video thumbnail is${hidden ? '' : ' not'} displayed for ${this.participant.name}`
154
+        });
155
+    }
124 156
 }

+ 21
- 0
tests/pageobjects/Notifications.ts Näytä tiedosto

@@ -5,6 +5,8 @@ const JOIN_ONE_TEST_ID = 'notify.connectedOneMember';
5 5
 const JOIN_TWO_TEST_ID = 'notify.connectedTwoMembers';
6 6
 const JOIN_MULTIPLE_TEST_ID = 'notify.connectedThreePlusMembers';
7 7
 const RAISE_HAND_NOTIFICATION_ID = 'notify.raisedHand';
8
+const REENABLE_SELF_VIEW_NOTIFICATION_ID = 'notify.selfViewTitle';
9
+const REENABLE_SELF_VIEW_CLOSE_NOTIFICATION = 'notify.selfViewTitle-dismiss';
8 10
 
9 11
 /**
10 12
  * Gathers all notifications logic in the UI and obtaining those.
@@ -41,4 +43,23 @@ export default class Notifications extends BasePageObject {
41 43
             [ `${JOIN_ONE_TEST_ID}-dismiss`, `${JOIN_TWO_TEST_ID}-dismiss`, `${JOIN_MULTIPLE_TEST_ID}-dismiss` ]
42 44
                 .map(async id => this.participant.driver.$(`#${id}"]`).click()));
43 45
     }
46
+
47
+    /**
48
+     * Waits for the self view notification to be displayed.
49
+     */
50
+    async waitForReEnableSelfViewNotification() {
51
+        const el
52
+            = this.participant.driver.$(`div[data-testid="${REENABLE_SELF_VIEW_NOTIFICATION_ID}"]`);
53
+
54
+        await el.waitForExist({ timeout: 2000 });
55
+        await el.waitForDisplayed();
56
+
57
+    }
58
+
59
+    /**
60
+     * Closes the self view notification.
61
+     */
62
+    async closeReEnableSelfViewNotification() {
63
+        await this.participant.driver.$(`div[data-testid="${REENABLE_SELF_VIEW_CLOSE_NOTIFICATION}"]`).click();
64
+    }
44 65
 }

+ 26
- 0
tests/pageobjects/SettingsDialog.ts Näytä tiedosto

@@ -1,7 +1,9 @@
1 1
 import BaseDialog from './BaseDialog';
2 2
 
3 3
 const EMAIL_FIELD = '#setEmail';
4
+const HIDE_SELF_VIEW_CHECKBOX = '//input[@name="hide-self-view"]';
4 5
 const SETTINGS_DIALOG_CONTENT = '.settings-pane';
6
+const X_PATH_MORE_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="General"]';
5 7
 const X_PATH_PROFILE_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="Profile"]';
6 8
 
7 9
 /**
@@ -34,6 +36,13 @@ export default class SettingsDialog extends BaseDialog {
34 36
         await this.openTab(X_PATH_PROFILE_TAB);
35 37
     }
36 38
 
39
+    /**
40
+     * Selects the Profile tab to be displayed.
41
+     */
42
+    async openMoreTab() {
43
+        await this.openTab(X_PATH_MORE_TAB);
44
+    }
45
+
37 46
     /**
38 47
      * Enters the passed in email into the email field.
39 48
      * @param email
@@ -59,4 +68,21 @@ export default class SettingsDialog extends BaseDialog {
59 68
     async submit() {
60 69
         await this.clickOkButton();
61 70
     }
71
+
72
+    /**
73
+     * Sets the state checked/selected of a checkbox in the settings dialog.
74
+     */
75
+    async setHideSelfView(hideSelfView: boolean) {
76
+        await this.openMoreTab();
77
+
78
+        const checkbox = this.participant.driver.$(HIDE_SELF_VIEW_CHECKBOX);
79
+
80
+        await checkbox.waitForExist();
81
+
82
+        if (hideSelfView !== await checkbox.isSelected()) {
83
+            // we show a div with svg and text after the input and those elements grab the click
84
+            // so we need to click on the parent element
85
+            await this.participant.driver.$(`${HIDE_SELF_VIEW_CHECKBOX}//ancestor::div[1]`).click();
86
+        }
87
+    }
62 88
 }

+ 37
- 0
tests/pageobjects/Toolbar.ts Näytä tiedosto

@@ -5,11 +5,15 @@ const AUDIO_UNMUTE = 'Unmute microphone';
5 5
 const CHAT = 'Open chat';
6 6
 const CLOSE_CHAT = 'Close chat';
7 7
 const CLOSE_PARTICIPANTS_PANE = 'Close participants pane';
8
+const HANGUP = 'Leave the meeting';
8 9
 const OVERFLOW_MENU = 'More actions menu';
9 10
 const OVERFLOW = 'More actions';
10 11
 const PARTICIPANTS = 'Open participants pane';
11 12
 const PROFILE = 'Edit your profile';
12 13
 const RAISE_HAND = 'Raise your hand';
14
+const SETTINGS = 'Open settings';
15
+const ENTER_TILE_VIEW_BUTTON = 'Enter tile view';
16
+const EXIT_TILE_VIEW_BUTTON = 'Exit tile view';
13 17
 const VIDEO_QUALITY = 'Manage video quality';
14 18
 const VIDEO_MUTE = 'Stop camera';
15 19
 const VIDEO_UNMUTE = 'Start camera';
@@ -160,6 +164,35 @@ export default class Toolbar extends BasePageObject {
160 164
         await this.getButton(CLOSE_CHAT).click();
161 165
     }
162 166
 
167
+    /**
168
+     * Clicks on the tile view button which enables tile layout.
169
+     */
170
+    async clickEnterTileViewButton() {
171
+        await this.getButton(ENTER_TILE_VIEW_BUTTON).click();
172
+    }
173
+
174
+    /**
175
+     * Clicks on the tile view button which exits tile layout.
176
+     */
177
+    async clickExitTileViewButton() {
178
+        await this.getButton(EXIT_TILE_VIEW_BUTTON).click();
179
+    }
180
+
181
+    /**
182
+     * Clicks on the hangup button that ends the conference.
183
+     */
184
+    async clickHangupButton(): Promise<void> {
185
+        this.participant.log('Clicking on: Hangup Button');
186
+        await this.getButton(HANGUP).click();
187
+    }
188
+
189
+    /**
190
+     * Clicks on the settings toolbar button which opens or closes the settings panel.
191
+     */
192
+    async clickSettingsButton() {
193
+        await this.clickButtonInOverflowMenu(SETTINGS);
194
+    }
195
+
163 196
     /**
164 197
      * Ensure the overflow menu is open and clicks on a specified button.
165 198
      * @param accessibilityLabel The accessibility label of the button to be clicked.
@@ -168,6 +201,10 @@ export default class Toolbar extends BasePageObject {
168 201
     private async clickButtonInOverflowMenu(accessibilityLabel: string) {
169 202
         await this.openOverflowMenu();
170 203
 
204
+        // sometimes the overflow button tooltip is over the last entry in the menu,
205
+        // so let's move focus away before clicking the button
206
+        await this.participant.driver.$('#overflow-context-menu').moveTo();
207
+
171 208
         this.participant.log(`Clicking on: ${accessibilityLabel}`);
172 209
         await this.getButton(accessibilityLabel).click();
173 210
 

+ 45
- 0
tests/specs/2way/displayName.spec.ts Näytä tiedosto

@@ -0,0 +1,45 @@
1
+import { ensureTwoParticipants } from '../../helpers/participants';
2
+
3
+describe('DisplayName - ', () => {
4
+    it('joining the meeting', async () => {
5
+        await ensureTwoParticipants(ctx, {
6
+            skipDisplayName: true
7
+        });
8
+    });
9
+
10
+    it('check change', async () => {
11
+        const { p1, p2 } = ctx;
12
+
13
+        // default remote display name
14
+        const defaultDisplayName = await p1.driver.execute(() => config.defaultRemoteDisplayName);
15
+        const p1EndpointId = await p1.getEndpointId();
16
+        const p2EndpointId = await p2.getEndpointId();
17
+
18
+        // Checks whether default display names are set and shown, when both sides still miss the display name.
19
+        expect(await p1.getFilmstrip().getRemoteDisplayName(p2EndpointId)).toBe(defaultDisplayName);
20
+        expect(await p2.getFilmstrip().getRemoteDisplayName(p1EndpointId)).toBe(defaultDisplayName);
21
+
22
+        const randomName = `Name${Math.trunc(Math.random() * 1_000_000)}`;
23
+
24
+        await p2.setLocalDisplayName(randomName);
25
+        expect(await p2.getLocalDisplayName()).toBe(randomName);
26
+        expect(await p1.getFilmstrip().getRemoteDisplayName(p2EndpointId)).toBe(randomName);
27
+    });
28
+
29
+    it('check persistence', async () => {
30
+        const { p2 } = ctx;
31
+        const randomName = `Name${Math.trunc(Math.random() * 1_000_000)}`;
32
+
33
+        await p2.setLocalDisplayName(randomName);
34
+
35
+        expect(await p2.getLocalDisplayName()).toBe(randomName);
36
+
37
+        await p2.hangup();
38
+
39
+        await ensureTwoParticipants(ctx, {
40
+            skipDisplayName: true
41
+        });
42
+
43
+        expect(await p2.getLocalDisplayName()).toBe(randomName);
44
+    });
45
+});

+ 22
- 0
tests/specs/2way/endConference.spec.ts Näytä tiedosto

@@ -0,0 +1,22 @@
1
+import { ensureTwoParticipants } from '../../helpers/participants';
2
+
3
+describe('End Conference - ', () => {
4
+    it('joining the meeting', async () => {
5
+        await ensureTwoParticipants(ctx);
6
+    });
7
+
8
+    it('hangup call and check', async () => {
9
+        const { p1 } = ctx;
10
+        const url = await p1.driver.getUrl();
11
+
12
+        await p1.getToolbar().clickHangupButton();
13
+
14
+        await p1.driver.waitUntil(
15
+            async () => await p1.driver.getUrl() !== url,
16
+            {
17
+                timeout: 5000,
18
+                timeoutMsg: 'p1 did not navigate away from the conference'
19
+            }
20
+        );
21
+    });
22
+});

+ 70
- 0
tests/specs/2way/selfView.spec.ts Näytä tiedosto

@@ -0,0 +1,70 @@
1
+import type { Participant } from '../../helpers/Participant';
2
+import { ensureTwoParticipants } from '../../helpers/participants';
3
+
4
+describe('Self view - ', () => {
5
+    it('joining the meeting', async () => {
6
+        await ensureTwoParticipants(ctx);
7
+    });
8
+
9
+    it('hide from menu', async () => {
10
+        const { p1 } = ctx;
11
+
12
+        await checkSelfViewHidden(p1, false);
13
+
14
+        await p1.getFilmstrip().hideSelfView();
15
+
16
+        await checkSelfViewHidden(p1, true, true);
17
+
18
+        await p1.getToolbar().clickEnterTileViewButton();
19
+
20
+        await checkSelfViewHidden(p1, true);
21
+    });
22
+
23
+    it('show from settings', async () => {
24
+        const { p1 } = ctx;
25
+
26
+        await toggleSelfViewFromSettings(p1, false);
27
+
28
+        await checkSelfViewHidden(p1, false);
29
+    });
30
+
31
+    it('hide from settings', async () => {
32
+        const { p1 } = ctx;
33
+
34
+        await toggleSelfViewFromSettings(p1, true);
35
+        await checkSelfViewHidden(p1, true, true);
36
+    });
37
+
38
+    it('check in alone meeting', async () => {
39
+        const { p1, p2 } = ctx;
40
+
41
+        await checkSelfViewHidden(p1, true);
42
+        await p2.hangup();
43
+        await checkSelfViewHidden(p1, true);
44
+    });
45
+});
46
+
47
+/**
48
+ * Toggles the self view option from the settings dialog.
49
+ */
50
+async function toggleSelfViewFromSettings(participant: Participant, hide: boolean) {
51
+    await participant.getToolbar().clickSettingsButton();
52
+
53
+    const settings = participant.getSettingsDialog();
54
+
55
+    await settings.waitForDisplay();
56
+    await settings.setHideSelfView(hide);
57
+    await settings.submit();
58
+}
59
+
60
+/**
61
+ * Checks whether the local self view is displayed or not.
62
+ */
63
+async function checkSelfViewHidden(participant: Participant, hidden: boolean, checkNotification = false) {
64
+    if (checkNotification) {
65
+        await participant.getNotifications().waitForReEnableSelfViewNotification();
66
+        await participant.getNotifications().closeReEnableSelfViewNotification();
67
+    }
68
+
69
+    await participant.getFilmstrip().assertSelfViewIsHidden(hidden);
70
+}

+ 1
- 1
tests/wdio.conf.ts Näytä tiedosto

@@ -47,7 +47,7 @@ if (process.env.HEADLESS === 'true') {
47 47
     chromeArgs.push('--window-size=1280,720');
48 48
 }
49 49
 if (process.env.VIDEO_CAPTURE_FILE) {
50
-    chromeArgs.push(`use-file-for-fake-video-capture=${process.env.VIDEO_CAPTURE_FILE}`);
50
+    chromeArgs.push(`--use-file-for-fake-video-capture=${process.env.VIDEO_CAPTURE_FILE}`);
51 51
 }
52 52
 
53 53
 const chromePreferences = {

Loading…
Peruuta
Tallenna