Sfoglia il codice sorgente

feat(tests): Adds breakout tests. (#15414)

* feat(tests): Introduces BasePageObject.

* fix(tests): Use wdio aria selector where possible.

* fix(tests): Correct test exclusion for Firefox.

* fix(tests): Rearrange code.

* feat(tests): Adds breakout tests.
factor2
Дамян Минков 6 mesi fa
parent
commit
c6cce9253c
Nessun account collegato all'indirizzo email del committer

+ 26
- 0
tests/helpers/Participant.ts Vedi File

4
 
4
 
5
 import { IConfig } from '../../react/features/base/config/configType';
5
 import { IConfig } from '../../react/features/base/config/configType';
6
 import { urlObjectToString } from '../../react/features/base/util/uri';
6
 import { urlObjectToString } from '../../react/features/base/util/uri';
7
+import BreakoutRooms from '../pageobjects/BreakoutRooms';
7
 import Filmstrip from '../pageobjects/Filmstrip';
8
 import Filmstrip from '../pageobjects/Filmstrip';
8
 import IframeAPI from '../pageobjects/IframeAPI';
9
 import IframeAPI from '../pageobjects/IframeAPI';
9
 import Notifications from '../pageobjects/Notifications';
10
 import Notifications from '../pageobjects/Notifications';
251
             && APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
252
             && APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
252
     }
253
     }
253
 
254
 
255
+    /**
256
+     * Checks if the meeting supports breakout rooms.
257
+     */
258
+    async isBreakoutRoomsSupported() {
259
+        return await this.driver.execute(() => typeof APP !== 'undefined'
260
+            && APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
261
+    }
262
+
263
+    /**
264
+     * Checks if the participant is in breakout room.
265
+     */
266
+    async isInBreakoutRoom() {
267
+        return await this.driver.execute(() => typeof APP !== 'undefined'
268
+            && APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isBreakoutRoom());
269
+    }
270
+
254
     /**
271
     /**
255
      * Waits to join the muc.
272
      * Waits to join the muc.
256
      *
273
      *
321
         });
338
         });
322
     }
339
     }
323
 
340
 
341
+    /**
342
+     * Returns the BreakoutRooms for this participant.
343
+     *
344
+     * @returns {BreakoutRooms}
345
+     */
346
+    getBreakoutRooms(): BreakoutRooms {
347
+        return new BreakoutRooms(this);
348
+    }
349
+
324
     /**
350
     /**
325
      * Returns the toolbar for this participant.
351
      * Returns the toolbar for this participant.
326
      *
352
      *

+ 18
- 0
tests/helpers/participants.ts Vedi File

6
 import { Participant } from './Participant';
6
 import { Participant } from './Participant';
7
 import { IContext, IJoinOptions } from './types';
7
 import { IContext, IJoinOptions } from './types';
8
 
8
 
9
+const SUBJECT_XPATH = '//div[starts-with(@class, "subject-text")]';
10
+
9
 /**
11
 /**
10
  * Ensure that there is on participant.
12
  * Ensure that there is on participant.
11
  *
13
  *
237
         resource: domainParts.length > 0 ? domainParts[1] : undefined
239
         resource: domainParts.length > 0 ? domainParts[1] : undefined
238
     };
240
     };
239
 }
241
 }
242
+
243
+/**
244
+ * Check the subject of the participant.
245
+ * @param participant
246
+ * @param subject
247
+ */
248
+export async function checkSubject(participant: Participant, subject: string) {
249
+    const localTile = participant.driver.$(SUBJECT_XPATH);
250
+
251
+    await localTile.waitForExist();
252
+    await localTile.moveTo();
253
+
254
+    const txt = await localTile.getText();
255
+
256
+    expect(txt.startsWith(subject)).toBe(true);
257
+}

+ 2
- 12
tests/pageobjects/AVModerationMenu.ts Vedi File

1
-import { Participant } from '../helpers/Participant';
1
+import BasePageObject from './BasePageObject';
2
 
2
 
3
 const START_AUDIO_MODERATION = 'participants-pane-context-menu-start-audio-moderation';
3
 const START_AUDIO_MODERATION = 'participants-pane-context-menu-start-audio-moderation';
4
 const STOP_AUDIO_MODERATION = 'participants-pane-context-menu-stop-audio-moderation';
4
 const STOP_AUDIO_MODERATION = 'participants-pane-context-menu-stop-audio-moderation';
8
 /**
8
 /**
9
  * Represents the Audio Video Moderation menu in the participants pane.
9
  * Represents the Audio Video Moderation menu in the participants pane.
10
  */
10
  */
11
-export default class AVModerationMenu {
12
-    private participant: Participant;
13
-
14
-    /**
15
-     * Represents the Audio Video Moderation menu in the participants pane.
16
-     * @param participant
17
-     */
18
-    constructor(participant: Participant) {
19
-        this.participant = participant;
20
-    }
21
-
11
+export default class AVModerationMenu extends BasePageObject {
22
     /**
12
     /**
23
      * Clicks the start audio moderation menu item.
13
      * Clicks the start audio moderation menu item.
24
      */
14
      */

+ 2
- 13
tests/pageobjects/BaseDialog.ts Vedi File

1
-import { Participant } from '../helpers/Participant';
1
+import BasePageObject from './BasePageObject';
2
 
2
 
3
 const CLOSE_BUTTON = 'modal-header-close-button';
3
 const CLOSE_BUTTON = 'modal-header-close-button';
4
 const OK_BUTTON = 'modal-dialog-ok-button';
4
 const OK_BUTTON = 'modal-dialog-ok-button';
6
 /**
6
 /**
7
  * Base class for all dialogs.
7
  * Base class for all dialogs.
8
  */
8
  */
9
-export default class BaseDialog {
10
-    participant: Participant;
11
-
12
-    /**
13
-     * Initializes for a participant.
14
-     *
15
-     * @param {Participant} participant - The participant.
16
-     */
17
-    constructor(participant: Participant) {
18
-        this.participant = participant;
19
-    }
20
-
9
+export default class BaseDialog extends BasePageObject {
21
     /**
10
     /**
22
      *  Clicks on the X (close) button.
11
      *  Clicks on the X (close) button.
23
      */
12
      */

+ 16
- 0
tests/pageobjects/BasePageObject.ts Vedi File

1
+import { Participant } from '../helpers/Participant';
2
+
3
+/**
4
+ * Represents the base page object.
5
+ * All page object has the current participant (holding the driver/browser session).
6
+ */
7
+export default class BasePageObject {
8
+    participant: Participant;
9
+
10
+    /**
11
+     * Represents the base page object.
12
+     */
13
+    constructor(participant: Participant) {
14
+        this.participant = participant;
15
+    }
16
+}

+ 230
- 0
tests/pageobjects/BreakoutRooms.ts Vedi File

1
+import { Participant } from '../helpers/Participant';
2
+
3
+import BaseDialog from './BaseDialog';
4
+import BasePageObject from './BasePageObject';
5
+
6
+const BREAKOUT_ROOMS_CLASS = 'breakout-room-container';
7
+const ADD_BREAKOUT_ROOM = 'Add breakout room';
8
+const MORE_LABEL = 'More';
9
+const LEAVE_ROOM_LABEL = 'Leave breakout room';
10
+const AUTO_ASSIGN_LABEL = 'Auto assign to breakout rooms';
11
+
12
+/**
13
+ * Represents a single breakout room and the operations for it.
14
+ */
15
+class BreakoutRoom extends BasePageObject {
16
+    title: string;
17
+    id: string;
18
+    count: number;
19
+
20
+    /**
21
+     * Constructs a breakout room.
22
+     */
23
+    constructor(participant: Participant, title: string, id: string) {
24
+        super(participant);
25
+
26
+        this.title = title;
27
+        this.id = id;
28
+
29
+        const tMatch = title.match(/.*\((.*)\)/);
30
+
31
+        if (tMatch) {
32
+            this.count = parseInt(tMatch[1], 10);
33
+        }
34
+    }
35
+
36
+    /**
37
+     * Returns room name.
38
+     */
39
+    get name() {
40
+        return this.title.split('(')[0].trim();
41
+    }
42
+
43
+    /**
44
+     * Returns the number of participants in the room.
45
+     */
46
+    get participantCount() {
47
+        return this.count;
48
+    }
49
+
50
+    /**
51
+     * Collapses the breakout room.
52
+     */
53
+    async collapse() {
54
+        const collapseElem = this.participant.driver.$(
55
+            `div[data-testid="${this.id}"]`);
56
+
57
+        await collapseElem.click();
58
+    }
59
+
60
+    /**
61
+     * Joins the breakout room.
62
+     */
63
+    async joinRoom() {
64
+        const joinButton = this.participant.driver
65
+            .$(`button[data-testid="join-room-${this.id}"]`);
66
+
67
+        await joinButton.waitForClickable();
68
+        await joinButton.click();
69
+    }
70
+
71
+    /**
72
+     * Removes the breakout room.
73
+     */
74
+    async removeRoom() {
75
+        await this.openContextMenu();
76
+
77
+        const removeButton = this.participant.driver.$(`#remove-room-${this.id}`);
78
+
79
+        await removeButton.waitForClickable();
80
+        await removeButton.click();
81
+    }
82
+
83
+    /**
84
+     * Renames the breakout room.
85
+     */
86
+    async renameRoom(newName: string) {
87
+        await this.openContextMenu();
88
+
89
+        const renameButton = this.participant.driver.$(`#rename-room-${this.id}`);
90
+
91
+        await renameButton.click();
92
+
93
+        const newNameInput = this.participant.driver.$('input[name="breakoutRoomName"]');
94
+
95
+        await newNameInput.waitForStable();
96
+        await newNameInput.setValue(newName);
97
+
98
+        await new BaseDialog(this.participant).clickOkButton();
99
+    }
100
+
101
+    /**
102
+     * Closes the breakout room.
103
+     */
104
+    async closeRoom() {
105
+        await this.openContextMenu();
106
+
107
+        const closeButton = this.participant.driver.$(`#close-room-${this.id}`);
108
+
109
+        await closeButton.waitForClickable();
110
+        await closeButton.click();
111
+    }
112
+
113
+    /**
114
+     * Opens the context menu.
115
+     * @private
116
+     */
117
+    private async openContextMenu() {
118
+        const listItem = this.participant.driver.$(`div[data-testid="${this.id}"]`);
119
+
120
+        await listItem.click();
121
+
122
+        const button = listItem.$(`aria/${MORE_LABEL}`);
123
+
124
+        await button.waitForClickable();
125
+        await button.click();
126
+    }
127
+}
128
+
129
+/**
130
+ * All breakout rooms objects and operations.
131
+ */
132
+export default class BreakoutRooms extends BasePageObject {
133
+    /**
134
+     * Returns the number of breakout rooms.
135
+     */
136
+    async getRoomsCount() {
137
+        const participantsPane = this.participant.getParticipantsPane();
138
+
139
+        if (!await participantsPane.isOpen()) {
140
+            await participantsPane.open();
141
+        }
142
+
143
+        return await this.participant.driver.$$(`.${BREAKOUT_ROOMS_CLASS}`).length;
144
+    }
145
+
146
+    /**
147
+     * Adds a breakout room.
148
+     */
149
+    async addBreakoutRoom() {
150
+        const participantsPane = this.participant.getParticipantsPane();
151
+
152
+        if (!await participantsPane.isOpen()) {
153
+            await participantsPane.open();
154
+        }
155
+
156
+        const addBreakoutButton = this.participant.driver.$(`aria/${ADD_BREAKOUT_ROOM}`);
157
+
158
+        await addBreakoutButton.waitForDisplayed();
159
+        await addBreakoutButton.click();
160
+    }
161
+
162
+    /**
163
+     * Returns all breakout rooms.
164
+     */
165
+    async getRooms(): Promise<BreakoutRoom[]> {
166
+        const rooms = this.participant.driver.$$(`.${BREAKOUT_ROOMS_CLASS}`);
167
+
168
+        return rooms.map(async room => new BreakoutRoom(
169
+                this.participant, await room.$('span').getText(), await room.getAttribute('data-testid')));
170
+    }
171
+
172
+    /**
173
+     * Leave by clicking the leave button in participant pane.
174
+     */
175
+    async leaveBreakoutRoom() {
176
+        const participantsPane = this.participant.getParticipantsPane();
177
+
178
+        if (!await participantsPane.isOpen()) {
179
+            await participantsPane.open();
180
+        }
181
+
182
+        const leaveButton = this.participant.driver.$(`aria/${LEAVE_ROOM_LABEL}`);
183
+
184
+        await leaveButton.isClickable();
185
+        await leaveButton.click();
186
+    }
187
+
188
+    /**
189
+     * Auto assign participants to breakout rooms.
190
+     */
191
+    async autoAssignToBreakoutRooms() {
192
+        const button = this.participant.driver.$(`aria/${AUTO_ASSIGN_LABEL}`);
193
+
194
+        await button.waitForClickable();
195
+        await button.click();
196
+    }
197
+
198
+    /**
199
+     * Tries to send a participant to a breakout room.
200
+     */
201
+    async sendParticipantToBreakoutRoom(participant: Participant, roomName: string) {
202
+        const participantsPane = this.participant.getParticipantsPane();
203
+
204
+        await participantsPane.selectParticipant(participant);
205
+        await participantsPane.openParticipantContextMenu(participant);
206
+
207
+        const sendButton = this.participant.driver.$(`aria/${roomName}`);
208
+
209
+        await sendButton.waitForClickable();
210
+        await sendButton.click();
211
+    }
212
+
213
+    // /**
214
+    //  * Open context menu for given participant.
215
+    //  */
216
+    // async openParticipantContextMenu(participant: Participant) {
217
+    //     const listItem = this.participant.driver.$(
218
+    //         `div[@id="participant-item-${await participant.getEndpointId()}"]`);
219
+    //
220
+    //     await listItem.waitForDisplayed();
221
+    //     await listItem.moveTo();
222
+    //
223
+    //     const button = listItem.$(`aria/${PARTICIPANT_MORE_LABEL}`);
224
+    //
225
+    //     await button.waitForClickable();
226
+    //     await button.click();
227
+    // }
228
+}
229
+
230
+

+ 2
- 12
tests/pageobjects/Filmstrip.ts Vedi File

1
 import { Participant } from '../helpers/Participant';
1
 import { Participant } from '../helpers/Participant';
2
 
2
 
3
 import BaseDialog from './BaseDialog';
3
 import BaseDialog from './BaseDialog';
4
+import BasePageObject from './BasePageObject';
4
 
5
 
5
 /**
6
 /**
6
  * Filmstrip elements.
7
  * Filmstrip elements.
7
  */
8
  */
8
-export default class Filmstrip {
9
-    private participant: Participant;
10
-
11
-    /**
12
-     * Initializes for a participant.
13
-     *
14
-     * @param {Participant} participant - The participant.
15
-     */
16
-    constructor(participant: Participant) {
17
-        this.participant = participant;
18
-    }
19
-
9
+export default class Filmstrip extends BasePageObject {
20
     /**
10
     /**
21
      * Asserts that {@code participant} shows or doesn't show the audio
11
      * Asserts that {@code participant} shows or doesn't show the audio
22
      * mute icon for the conference participant identified by
12
      * mute icon for the conference participant identified by

+ 3
- 12
tests/pageobjects/IframeAPI.ts Vedi File

1
-import { Participant } from '../helpers/Participant';
2
 import { LOG_PREFIX } from '../helpers/browserLogger';
1
 import { LOG_PREFIX } from '../helpers/browserLogger';
3
 
2
 
3
+import BasePageObject from './BasePageObject';
4
+
4
 /**
5
 /**
5
  * The Iframe API and helpers from iframeAPITest.html
6
  * The Iframe API and helpers from iframeAPITest.html
6
  */
7
  */
7
-export default class IframeAPI {
8
-    private participant: Participant;
9
-
10
-    /**
11
-     * Initializes for a participant.
12
-     * @param participant
13
-     */
14
-    constructor(participant: Participant) {
15
-        this.participant = participant;
16
-    }
17
-
8
+export default class IframeAPI extends BasePageObject {
18
     /**
9
     /**
19
      * Returns the json object from the iframeAPI helper.
10
      * Returns the json object from the iframeAPI helper.
20
      * @param event
11
      * @param event

+ 2
- 12
tests/pageobjects/Notifications.ts Vedi File

1
-import { Participant } from '../helpers/Participant';
1
+import BasePageObject from './BasePageObject';
2
 
2
 
3
 const ASK_TO_UNMUTE_NOTIFICATION_ID = 'notify.hostAskedUnmute';
3
 const ASK_TO_UNMUTE_NOTIFICATION_ID = 'notify.hostAskedUnmute';
4
 const JOIN_ONE_TEST_ID = 'notify.connectedOneMember';
4
 const JOIN_ONE_TEST_ID = 'notify.connectedOneMember';
9
 /**
9
 /**
10
  * Gathers all notifications logic in the UI and obtaining those.
10
  * Gathers all notifications logic in the UI and obtaining those.
11
  */
11
  */
12
-export default class Notifications {
13
-    private participant: Participant;
14
-
15
-    /**
16
-     * Represents the Audio Video Moderation menu in the participants pane.
17
-     * @param participant
18
-     */
19
-    constructor(participant: Participant) {
20
-        this.participant = participant;
21
-    }
22
-
12
+export default class Notifications extends BasePageObject {
23
     /**
13
     /**
24
      * Waits for the raised hand notification to be displayed.
14
      * Waits for the raised hand notification to be displayed.
25
      * The notification on moderators page when the participant tries to unmute.
15
      * The notification on moderators page when the participant tries to unmute.

+ 32
- 26
tests/pageobjects/ParticipantsPane.ts Vedi File

1
 import { Participant } from '../helpers/Participant';
1
 import { Participant } from '../helpers/Participant';
2
 
2
 
3
 import AVModerationMenu from './AVModerationMenu';
3
 import AVModerationMenu from './AVModerationMenu';
4
+import BasePageObject from './BasePageObject';
4
 
5
 
5
 /**
6
 /**
6
  * Classname of the closed/hidden participants pane
7
  * Classname of the closed/hidden participants pane
10
 /**
11
 /**
11
  * Represents the participants pane from the UI.
12
  * Represents the participants pane from the UI.
12
  */
13
  */
13
-export default class ParticipantsPane {
14
-    private participant: Participant;
15
-
16
-    /**
17
-     * Initializes for a participant.
18
-     *
19
-     * @param {Participant} participant - The participant.
20
-     */
21
-    constructor(participant: Participant) {
22
-        this.participant = participant;
23
-    }
24
-
14
+export default class ParticipantsPane extends BasePageObject {
25
     /**
15
     /**
26
      * Gets the audio video moderation menu.
16
      * Gets the audio video moderation menu.
27
      */
17
      */
138
         await this.participant.getNotifications().dismissAnyJoinNotification();
128
         await this.participant.getNotifications().dismissAnyJoinNotification();
139
 
129
 
140
         const participantId = await participantToUnmute.getEndpointId();
130
         const participantId = await participantToUnmute.getEndpointId();
141
-        const participantItem = this.participant.driver.$(`#participant-item-${participantId}`);
142
-
143
-        await participantItem.waitForExist();
144
-        await participantItem.waitForStable();
145
-        await participantItem.waitForDisplayed();
146
-        await participantItem.moveTo();
147
 
131
 
132
+        await this.selectParticipant(participantToUnmute);
148
         if (fromContextMenu) {
133
         if (fromContextMenu) {
149
-            const meetingParticipantMoreOptions = this.participant.driver
150
-                .$(`[data-testid="participant-more-options-${participantId}"]`);
151
-
152
-            await meetingParticipantMoreOptions.waitForExist();
153
-            await meetingParticipantMoreOptions.waitForDisplayed();
154
-            await meetingParticipantMoreOptions.waitForStable();
155
-            await meetingParticipantMoreOptions.moveTo();
156
-            await meetingParticipantMoreOptions.click();
134
+            await this.openParticipantContextMenu(participantToUnmute);
157
         }
135
         }
158
 
136
 
159
         const unmuteButton = this.participant.driver
137
         const unmuteButton = this.participant.driver
162
         await unmuteButton.waitForExist();
140
         await unmuteButton.waitForExist();
163
         await unmuteButton.click();
141
         await unmuteButton.click();
164
     }
142
     }
143
+
144
+    /**
145
+     * Open context menu for given participant.
146
+     */
147
+    async selectParticipant(participant: Participant) {
148
+        const participantId = await participant.getEndpointId();
149
+        const participantItem = this.participant.driver.$(`#participant-item-${participantId}`);
150
+
151
+        await participantItem.waitForExist();
152
+        await participantItem.waitForStable();
153
+        await participantItem.waitForDisplayed();
154
+        await participantItem.moveTo();
155
+    }
156
+
157
+    /**
158
+     * Open context menu for given participant.
159
+     */
160
+    async openParticipantContextMenu(participant: Participant) {
161
+        const participantId = await participant.getEndpointId();
162
+        const meetingParticipantMoreOptions = this.participant.driver
163
+            .$(`[data-testid="participant-more-options-${participantId}"]`);
164
+
165
+        await meetingParticipantMoreOptions.waitForExist();
166
+        await meetingParticipantMoreOptions.waitForDisplayed();
167
+        await meetingParticipantMoreOptions.waitForStable();
168
+        await meetingParticipantMoreOptions.moveTo();
169
+        await meetingParticipantMoreOptions.click();
170
+    }
165
 }
171
 }

+ 9
- 18
tests/pageobjects/Toolbar.ts Vedi File

1
-// eslint-disable-next-line no-unused-vars
2
-import { Participant } from '../helpers/Participant';
1
+import BasePageObject from './BasePageObject';
3
 
2
 
4
 const AUDIO_MUTE = 'Mute microphone';
3
 const AUDIO_MUTE = 'Mute microphone';
5
 const AUDIO_UNMUTE = 'Unmute microphone';
4
 const AUDIO_UNMUTE = 'Unmute microphone';
16
 /**
15
 /**
17
  * The toolbar elements.
16
  * The toolbar elements.
18
  */
17
  */
19
-export default class Toolbar {
20
-    private participant: Participant;
21
-
22
-    /**
23
-     * Creates toolbar for a participant.
24
-     *
25
-     * @param {Participant} participant - The participants.
26
-     */
27
-    constructor(participant: Participant) {
28
-        this.participant = participant;
29
-    }
30
-
18
+export default class Toolbar extends BasePageObject {
31
     /**
19
     /**
32
      * Returns the button.
20
      * Returns the button.
33
      *
21
      *
36
      * @private
24
      * @private
37
      */
25
      */
38
     private getButton(accessibilityCSSSelector: string) {
26
     private getButton(accessibilityCSSSelector: string) {
39
-        return this.participant.driver.$(`[aria-label^="${accessibilityCSSSelector}"]`);
27
+        return this.participant.driver.$(`aria/${accessibilityCSSSelector}`);
40
     }
28
     }
41
 
29
 
42
     /**
30
     /**
125
      */
113
      */
126
     async clickParticipantsPaneButton(): Promise<void> {
114
     async clickParticipantsPaneButton(): Promise<void> {
127
         this.participant.log('Clicking on: Participants pane Button');
115
         this.participant.log('Clicking on: Participants pane Button');
128
-        await this.getButton(PARTICIPANTS).click();
116
+
117
+        // Special case for participants pane button, as it contains the number of participants and its label
118
+        // is changing
119
+        await this.participant.driver.$(`[aria-label^="${PARTICIPANTS}"]`).click();
129
     }
120
     }
130
 
121
 
131
     /**
122
     /**
170
      * @private
161
      * @private
171
      */
162
      */
172
     private async isOverflowMenuOpen() {
163
     private async isOverflowMenuOpen() {
173
-        return await this.participant.driver.$$(`[aria-label^="${OVERFLOW_MENU}"]`).length > 0;
164
+        return await this.participant.driver.$$(`aria/${OVERFLOW_MENU}`).length > 0;
174
     }
165
     }
175
 
166
 
176
     /**
167
     /**
215
      * @private
206
      * @private
216
      */
207
      */
217
     private async waitForOverFlowMenu(visible: boolean) {
208
     private async waitForOverFlowMenu(visible: boolean) {
218
-        await this.participant.driver.$(`[aria-label^="${OVERFLOW_MENU}"]`).waitForDisplayed({
209
+        await this.getButton(OVERFLOW_MENU).waitForDisplayed({
219
             reverse: !visible,
210
             reverse: !visible,
220
             timeout: 3000,
211
             timeout: 3000,
221
             timeoutMsg: `Overflow menu is not ${visible ? 'visible' : 'hidden'}`
212
             timeoutMsg: `Overflow menu is not ${visible ? 'visible' : 'hidden'}`

+ 30
- 30
tests/specs/2way/audioOnly.spec.ts Vedi File

33
         await setAudioOnlyAndCheck(false);
33
         await setAudioOnlyAndCheck(false);
34
     });
34
     });
35
 
35
 
36
-    /**
37
-     * Toggles the audio only state of a p1 participant and verifies participant sees the audio only label and that
38
-     * p2 participant sees a video mute state for the former.
39
-     * @param enable
40
-     */
41
-    async function setAudioOnlyAndCheck(enable: boolean) {
42
-        const { p1 } = ctx;
43
-
44
-        await p1.getVideoQualityDialog().setVideoQuality(enable);
45
-
46
-        await verifyVideoMute(enable);
47
-
48
-        await p1.driver.$('//div[@id="videoResolutionLabel"][contains(@class, "audio-only")]')
49
-            .waitForDisplayed({ reverse: !enable });
50
-    }
51
-
52
-    /**
53
-     * Verifies that p1 and p2 see p1 as video muted or not.
54
-     * @param muted
55
-     */
56
-    async function verifyVideoMute(muted: boolean) {
57
-        const { p1, p2 } = ctx;
58
-
59
-        // Verify the observer sees the testee in the desired muted state.
60
-        await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
61
-
62
-        // Verify the testee sees itself in the desired muted state.
63
-        await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
64
-    }
65
-
66
     /**
36
     /**
67
      * Mutes video on participant1, toggles audio-only twice and then verifies if both participants see participant1
37
      * Mutes video on participant1, toggles audio-only twice and then verifies if both participants see participant1
68
      * as video muted.
38
      * as video muted.
92
         await verifyVideoMute(false);
62
         await verifyVideoMute(false);
93
     });
63
     });
94
 });
64
 });
65
+
66
+/**
67
+ * Toggles the audio only state of a p1 participant and verifies participant sees the audio only label and that
68
+ * p2 participant sees a video mute state for the former.
69
+ * @param enable
70
+ */
71
+async function setAudioOnlyAndCheck(enable: boolean) {
72
+    const { p1 } = ctx;
73
+
74
+    await p1.getVideoQualityDialog().setVideoQuality(enable);
75
+
76
+    await verifyVideoMute(enable);
77
+
78
+    await p1.driver.$('//div[@id="videoResolutionLabel"][contains(@class, "audio-only")]')
79
+        .waitForDisplayed({ reverse: !enable });
80
+}
81
+
82
+/**
83
+ * Verifies that p1 and p2 see p1 as video muted or not.
84
+ * @param muted
85
+ */
86
+async function verifyVideoMute(muted: boolean) {
87
+    const { p1, p2 } = ctx;
88
+
89
+    // Verify the observer sees the testee in the desired muted state.
90
+    await p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
91
+
92
+    // Verify the testee sees itself in the desired muted state.
93
+    await p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(p1, !muted);
94
+}

+ 464
- 0
tests/specs/3way/breakoutRooms.spec.ts Vedi File

1
+import type { ChainablePromiseElement } from 'webdriverio';
2
+
3
+import type { Participant } from '../../helpers/Participant';
4
+import { checkSubject, ensureThreeParticipants, ensureTwoParticipants } from '../../helpers/participants';
5
+
6
+const MAIN_ROOM_NAME = 'Main room';
7
+const BREAKOUT_ROOMS_LIST_ID = 'breakout-rooms-list';
8
+const LIST_ITEM_CONTAINER = 'list-item-container';
9
+
10
+describe('BreakoutRooms ', () => {
11
+    it('check support', async () => {
12
+        await ensureTwoParticipants(ctx);
13
+
14
+        if (!await ctx.p1.isBreakoutRoomsSupported()) {
15
+            ctx.skipSuiteTests = true;
16
+        }
17
+    });
18
+
19
+    it('add breakout room', async () => {
20
+        const { p1, p2 } = ctx;
21
+        const p1BreakoutRooms = p1.getBreakoutRooms();
22
+
23
+        // there should be no breakout rooms initially, list is sent with a small delay
24
+        await p1.driver.pause(2000);
25
+        expect(await p1BreakoutRooms.getRoomsCount()).toBe(0);
26
+
27
+        // add one breakout room
28
+        await p1BreakoutRooms.addBreakoutRoom();
29
+
30
+        await p1.driver.waitUntil(
31
+            async () => await p1BreakoutRooms.getRoomsCount() === 1, {
32
+                timeout: 2000,
33
+                timeoutMsg: 'No breakout room added for p1'
34
+            });
35
+
36
+
37
+        // second participant should also see one breakout room
38
+        await p2.driver.waitUntil(
39
+            async () => await p2.getBreakoutRooms().getRoomsCount() === 1, {
40
+                timeout: 2000,
41
+                timeoutMsg: 'No breakout room seen by p2'
42
+            });
43
+    });
44
+
45
+    it('join breakout room', async () => {
46
+        const { p1, p2 } = ctx;
47
+        const p1BreakoutRooms = p1.getBreakoutRooms();
48
+
49
+        // there should be one breakout room
50
+        await p1.driver.waitUntil(
51
+            async () => await p1BreakoutRooms.getRoomsCount() === 1, {
52
+                timeout: 1000,
53
+                timeoutMsg: 'No breakout room seen by p1'
54
+            });
55
+
56
+        const roomsList = await p1BreakoutRooms.getRooms();
57
+
58
+        expect(roomsList.length).toBe(1);
59
+
60
+        // join the room
61
+        await roomsList[0].joinRoom();
62
+
63
+        // the participant should see the main room as the only breakout room
64
+        await p1.driver.waitUntil(
65
+            async () => {
66
+                if (await p1BreakoutRooms.getRoomsCount() !== 1) {
67
+                    return false;
68
+                }
69
+
70
+                const list = await p1BreakoutRooms.getRooms();
71
+
72
+                if (list?.length !== 1) {
73
+                    return false;
74
+                }
75
+
76
+                return list[0].name === MAIN_ROOM_NAME;
77
+            }, {
78
+                timeout: 2000,
79
+                timeoutMsg: 'P1 did not join breakout room'
80
+            });
81
+
82
+        // the second participant should see one participant in the breakout room
83
+        await p2.driver.waitUntil(
84
+            async () => {
85
+                const list = await p2.getBreakoutRooms().getRooms();
86
+
87
+                if (list?.length !== 1) {
88
+                    return false;
89
+                }
90
+
91
+                return list[0].participantCount === 1;
92
+            }, {
93
+                timeout: 2000,
94
+                timeoutMsg: 'P2 is not seeing p1 in the breakout room'
95
+            });
96
+    });
97
+
98
+    it('leave breakout room', async () => {
99
+        const { p1, p2 } = ctx;
100
+        const p1BreakoutRooms = p1.getBreakoutRooms();
101
+
102
+        // leave room
103
+        await p1BreakoutRooms.leaveBreakoutRoom();
104
+
105
+        // there should be one breakout room and that should not be the main room
106
+        await p1.driver.waitUntil(
107
+            async () => {
108
+                if (await p1BreakoutRooms.getRoomsCount() !== 1) {
109
+                    return false;
110
+                }
111
+
112
+                const list = await p1BreakoutRooms.getRooms();
113
+
114
+                if (list?.length !== 1) {
115
+                    return false;
116
+                }
117
+
118
+                return list[0].name !== MAIN_ROOM_NAME;
119
+            }, {
120
+                timeout: 2000,
121
+                timeoutMsg: 'P1 did not leave breakout room'
122
+            });
123
+
124
+        // the second participant should see no participants in the breakout room
125
+        await p2.driver.waitUntil(
126
+            async () => {
127
+                const list = await p2.getBreakoutRooms().getRooms();
128
+
129
+                if (list?.length !== 1) {
130
+                    return false;
131
+                }
132
+
133
+                return list[0].participantCount === 0;
134
+            }, {
135
+                timeout: 2000,
136
+                timeoutMsg: 'P2 is seeing p1 in the breakout room'
137
+            });
138
+    });
139
+
140
+    it('remove breakout room', async () => {
141
+        const { p1, p2 } = ctx;
142
+        const p1BreakoutRooms = p1.getBreakoutRooms();
143
+
144
+        // remove the room
145
+        await (await p1BreakoutRooms.getRooms())[0].removeRoom();
146
+
147
+        // there should be no breakout rooms
148
+        await p1.driver.waitUntil(
149
+            async () => await p1BreakoutRooms.getRoomsCount() === 0, {
150
+                timeout: 2000,
151
+                timeoutMsg: 'Breakout room was not removed for p1'
152
+            });
153
+
154
+        // the second participant should also see no breakout rooms
155
+        await p2.driver.waitUntil(
156
+            async () => await p2.getBreakoutRooms().getRoomsCount() === 0, {
157
+                timeout: 2000,
158
+                timeoutMsg: 'Breakout room was not removed for p2'
159
+            });
160
+    });
161
+
162
+    it('auto assign', async () => {
163
+        await ensureThreeParticipants(ctx);
164
+        const { p1, p2 } = ctx;
165
+        const p1BreakoutRooms = p1.getBreakoutRooms();
166
+
167
+        // create two rooms
168
+        await p1BreakoutRooms.addBreakoutRoom();
169
+        await p1BreakoutRooms.addBreakoutRoom();
170
+
171
+        // there should be two breakout rooms
172
+        await p1.driver.waitUntil(
173
+            async () => await p1BreakoutRooms.getRoomsCount() === 2, {
174
+                timeout: 2000,
175
+                timeoutMsg: 'Breakout room was not created by p1'
176
+            });
177
+
178
+        // auto assign participants to rooms
179
+        await p1BreakoutRooms.autoAssignToBreakoutRooms();
180
+
181
+        // each room should have one participant
182
+        await p1.driver.waitUntil(
183
+            async () => {
184
+                if (await p1BreakoutRooms.getRoomsCount() !== 2) {
185
+                    return false;
186
+                }
187
+
188
+                const list = await p1BreakoutRooms.getRooms();
189
+
190
+                if (list?.length !== 2) {
191
+                    return false;
192
+                }
193
+
194
+                return list[0].participantCount === 1 && list[1].participantCount === 1;
195
+            }, {
196
+                timeout: 2000,
197
+                timeoutMsg: 'P1 did not auto assigned participants to breakout rooms'
198
+            });
199
+
200
+        // the second participant should see one participant in the main room
201
+        const p2BreakoutRooms = p2.getBreakoutRooms();
202
+
203
+        await p2.driver.waitUntil(
204
+            async () => {
205
+                if (await p2BreakoutRooms.getRoomsCount() !== 2) {
206
+                    return false;
207
+                }
208
+
209
+                const list = await p2BreakoutRooms.getRooms();
210
+
211
+                if (list?.length !== 2) {
212
+                    return false;
213
+                }
214
+
215
+                return list[0].participantCount === 1 && list[1].participantCount === 1
216
+                    && (list[0].name === MAIN_ROOM_NAME || list[1].name === MAIN_ROOM_NAME);
217
+            }, {
218
+                timeout: 2000,
219
+                timeoutMsg: 'P2 is not seeing p1 in the main room'
220
+            });
221
+    });
222
+
223
+    it('close breakout room', async () => {
224
+        const { p1, p2, p3 } = ctx;
225
+        const p1BreakoutRooms = p1.getBreakoutRooms();
226
+
227
+        // there should be two non-empty breakout rooms
228
+        await p1.driver.waitUntil(
229
+            async () => {
230
+                if (await p1BreakoutRooms.getRoomsCount() !== 2) {
231
+                    return false;
232
+                }
233
+
234
+                const list = await p1BreakoutRooms.getRooms();
235
+
236
+                if (list?.length !== 2) {
237
+                    return false;
238
+                }
239
+
240
+                return list[0].participantCount === 1 && list[1].participantCount === 1;
241
+            }, {
242
+                timeout: 2000,
243
+                timeoutMsg: 'P1 is not seeing two breakout rooms'
244
+            });
245
+
246
+        // close the first room
247
+        await (await p1BreakoutRooms.getRooms())[0].closeRoom();
248
+
249
+        // there should be two rooms and first one should be empty
250
+        await p1.driver.waitUntil(
251
+            async () => {
252
+                if (await p1BreakoutRooms.getRoomsCount() !== 2) {
253
+                    return false;
254
+                }
255
+
256
+                const list = await p1BreakoutRooms.getRooms();
257
+
258
+                if (list?.length !== 2) {
259
+                    return false;
260
+                }
261
+
262
+                return list[0].participantCount === 0 || list[1].participantCount === 0;
263
+            }, {
264
+                timeout: 2000,
265
+                timeoutMsg: 'P1 is not seeing an empty breakout room'
266
+            });
267
+
268
+        // there should be two participants in the main room, either p2 or p3 got moved to the main room
269
+        const checkParticipants = async (p: Participant) => {
270
+            await p.driver.waitUntil(
271
+                async () => {
272
+                    const isInBreakoutRoom = await p.isInBreakoutRoom();
273
+                    const breakoutRooms = p.getBreakoutRooms();
274
+
275
+                    if (isInBreakoutRoom) {
276
+                        if (await breakoutRooms.getRoomsCount() !== 2) {
277
+                            return false;
278
+                        }
279
+
280
+                        const list = await breakoutRooms.getRooms();
281
+
282
+                        if (list?.length !== 2) {
283
+                            return false;
284
+                        }
285
+
286
+                        return list.every(r => { // eslint-disable-line arrow-body-style
287
+                            return r.name === MAIN_ROOM_NAME ? r.participantCount === 2 : r.participantCount === 0;
288
+                        });
289
+                    }
290
+
291
+                    if (await breakoutRooms.getRoomsCount() !== 2) {
292
+                        return false;
293
+                    }
294
+
295
+                    const list = await breakoutRooms.getRooms();
296
+
297
+                    if (list?.length !== 2) {
298
+                        return false;
299
+                    }
300
+
301
+                    return list[0].participantCount + list[1].participantCount === 1;
302
+                }, {
303
+                    timeout: 2000,
304
+                    timeoutMsg: `${p.name} is not seeing an empty breakout room and one with one participant`
305
+                });
306
+        };
307
+
308
+        await checkParticipants(p2);
309
+        await checkParticipants(p3);
310
+    });
311
+
312
+    it('send participants to breakout room', async () => {
313
+        await Promise.all([ ctx.p1.hangup(), ctx.p2.hangup(), ctx.p3.hangup() ]);
314
+
315
+        // because the participants rejoin so fast, the meeting is not properly ended,
316
+        // so the previous breakout rooms would still be there.
317
+        // To avoid this issue we use a different meeting
318
+        ctx.roomName += '-breakout-rooms';
319
+
320
+        await ensureTwoParticipants(ctx);
321
+        const { p1, p2 } = ctx;
322
+        const p1BreakoutRooms = p1.getBreakoutRooms();
323
+
324
+        // there should be no breakout rooms
325
+        expect(await p1BreakoutRooms.getRoomsCount()).toBe(0);
326
+
327
+        // add one breakout room
328
+        await p1BreakoutRooms.addBreakoutRoom();
329
+
330
+        // there should be one empty room
331
+        await p1.driver.waitUntil(
332
+            async () => await p1BreakoutRooms.getRoomsCount() === 1
333
+                && (await p1BreakoutRooms.getRooms())[0].participantCount === 0, {
334
+                timeout: 2000,
335
+                timeoutMsg: 'No breakout room added for p1'
336
+            });
337
+
338
+        // send the second participant to the first breakout room
339
+        await p1BreakoutRooms.sendParticipantToBreakoutRoom(p2, (await p1BreakoutRooms.getRooms())[0].name);
340
+
341
+        // there should be one room with one participant
342
+        await p1.driver.waitUntil(
343
+            async () => {
344
+                const list = await p1BreakoutRooms.getRooms();
345
+
346
+                if (list?.length !== 1) {
347
+                    return false;
348
+                }
349
+
350
+                return list[0].participantCount === 1;
351
+            }, {
352
+                timeout: 2000,
353
+                timeoutMsg: 'P1 is not seeing p2 in the breakout room'
354
+            });
355
+    });
356
+
357
+    it('collapse breakout room', async () => {
358
+        const { p1 } = ctx;
359
+        const p1BreakoutRooms = p1.getBreakoutRooms();
360
+
361
+        // there should be one breakout room with one participant
362
+        await p1.driver.waitUntil(
363
+            async () => {
364
+                const list = await p1BreakoutRooms.getRooms();
365
+
366
+                if (list?.length !== 1) {
367
+                    return false;
368
+                }
369
+
370
+                return list[0].participantCount === 1;
371
+            }, {
372
+                timeout: 2000,
373
+                timeoutMsg: 'P1 is not seeing p2 in the breakout room'
374
+            });
375
+
376
+        // get id of the breakout room participant
377
+        const breakoutList = p1.driver.$(`#${BREAKOUT_ROOMS_LIST_ID}`);
378
+        const breakoutRoomItem = await breakoutList.$$(`.${LIST_ITEM_CONTAINER}`).find(
379
+            async el => {
380
+                const id = await el.getAttribute('id');
381
+
382
+                return id !== '' && id !== null;
383
+            }) as ChainablePromiseElement;
384
+
385
+        const pId = await breakoutRoomItem.getAttribute('id');
386
+        const breakoutParticipant = p1.driver.$(`//div[@id="${pId}"]`);
387
+
388
+        expect(await breakoutParticipant.isDisplayed()).toBe(true);
389
+
390
+        // collapse the first
391
+        await (await p1BreakoutRooms.getRooms())[0].collapse();
392
+
393
+        // the participant should not be visible
394
+        expect(await breakoutParticipant.isDisplayed()).toBe(false);
395
+
396
+        // the collapsed room should still have one participant
397
+        expect((await p1BreakoutRooms.getRooms())[0].participantCount).toBe(1);
398
+    });
399
+
400
+    it('rename breakout room', async () => {
401
+        const myNewRoomName = `breakout-${crypto.randomUUID()}`;
402
+        const { p1, p2 } = ctx;
403
+        const p1BreakoutRooms = p1.getBreakoutRooms();
404
+
405
+        // let's rename breakout room and see it in local and remote
406
+        await (await p1BreakoutRooms.getRooms())[0].renameRoom(myNewRoomName);
407
+
408
+        await p1.driver.waitUntil(
409
+            async () => {
410
+                const list = await p1BreakoutRooms.getRooms();
411
+
412
+                if (list?.length !== 1) {
413
+                    return false;
414
+                }
415
+
416
+                return list[0].name === myNewRoomName;
417
+            }, {
418
+                timeout: 2000,
419
+                timeoutMsg: 'The breakout room was not renamed for p1'
420
+            });
421
+
422
+        await checkSubject(p2, myNewRoomName);
423
+
424
+        // leave room
425
+        await p2.getBreakoutRooms().leaveBreakoutRoom();
426
+
427
+        // there should be one empty room
428
+        await p1.driver.waitUntil(
429
+            async () => {
430
+                const list = await p1BreakoutRooms.getRooms();
431
+
432
+                if (list?.length !== 1) {
433
+                    return false;
434
+                }
435
+
436
+                return list[0].participantCount === 0;
437
+            }, {
438
+                timeout: 2000,
439
+                timeoutMsg: 'The breakout room was not renamed for p1'
440
+            });
441
+
442
+        expect((await p2.getBreakoutRooms().getRooms())[0].name).toBe(myNewRoomName);
443
+
444
+        // send the second participant to the first breakout room
445
+        await p1BreakoutRooms.sendParticipantToBreakoutRoom(p2, myNewRoomName);
446
+
447
+        // there should be one room with one participant
448
+        await p1.driver.waitUntil(
449
+            async () => {
450
+                const list = await p1BreakoutRooms.getRooms();
451
+
452
+                if (list?.length !== 1) {
453
+                    return false;
454
+                }
455
+
456
+                return list[0].participantCount === 1;
457
+            }, {
458
+                timeout: 2000,
459
+                timeoutMsg: 'The breakout room was not rename for p1'
460
+            });
461
+
462
+        await checkSubject(p2, myNewRoomName);
463
+    });
464
+});

+ 1
- 4
tests/wdio.conf.ts Vedi File

178
                 return;
178
                 return;
179
             }
179
             }
180
 
180
 
181
-            // if (process.env.GRID_HOST_URL) {
182
-            // TODO: make sure we use uploadFile only with chrome (it does not work with FF),
183
-            // we need to test it with the grid and FF, does it work there
184
             const rpath = await bInstance.uploadFile('tests/resources/iframeAPITest.html');
181
             const rpath = await bInstance.uploadFile('tests/resources/iframeAPITest.html');
185
 
182
 
186
             // @ts-ignore
183
             // @ts-ignore
199
     after() {
196
     after() {
200
         const { ctx }: any = global;
197
         const { ctx }: any = global;
201
 
198
 
202
-        if (ctx.webhooksProxy) {
199
+        if (ctx?.webhooksProxy) {
203
             ctx.webhooksProxy.disconnect();
200
             ctx.webhooksProxy.disconnect();
204
         }
201
         }
205
     },
202
     },

+ 2
- 2
tests/wdio.dev.conf.ts Vedi File

1
 // wdio.dev.conf.ts
1
 // wdio.dev.conf.ts
2
 // extends the main configuration file for the development environment (make dev)
2
 // extends the main configuration file for the development environment (make dev)
3
 // it will connect to the webpack-dev-server running locally on port 8080
3
 // it will connect to the webpack-dev-server running locally on port 8080
4
-import { deepmerge } from 'deepmerge-ts';
4
+import { merge } from 'lodash-es';
5
 
5
 
6
 // @ts-ignore
6
 // @ts-ignore
7
 import { config as defaultConfig } from './wdio.conf.ts';
7
 import { config as defaultConfig } from './wdio.conf.ts';
8
 
8
 
9
-export const config = deepmerge(defaultConfig, {
9
+export const config = merge(defaultConfig, {
10
     baseUrl: 'https://127.0.0.1:8080/torture'
10
     baseUrl: 'https://127.0.0.1:8080/torture'
11
 }, { clone: false });
11
 }, { clone: false });

+ 29
- 4
tests/wdio.firefox.conf.ts Vedi File

19
     ffArgs.push('--headless');
19
     ffArgs.push('--headless');
20
 }
20
 }
21
 
21
 
22
+const ffExcludes = [
23
+    'specs/2way/iFrameParticipantsPresence.spec.ts', // FF does not support uploading files (uploadFile)
24
+    'specs/3way/activeSpeaker.spec.ts' // FF does not support setting a file as mic input
25
+];
26
+
22
 export const config = merge(defaultConfig, {
27
 export const config = merge(defaultConfig, {
23
-    exclude: [
24
-        'specs/2way/iFrameParticipantsPresence.spec.ts', // FF does not support uploading files (uploadFile)
25
-        'specs/3way/activeSpeaker.spec.ts' // FF does not support setting a file as mic input
26
-    ],
27
     capabilities: {
28
     capabilities: {
28
         participant1: {
29
         participant1: {
29
             capabilities: {
30
             capabilities: {
34
                 },
35
                 },
35
                 acceptInsecureCerts: process.env.ALLOW_INSECURE_CERTS === 'true'
36
                 acceptInsecureCerts: process.env.ALLOW_INSECURE_CERTS === 'true'
36
             }
37
             }
38
+        },
39
+        participant2: {
40
+            capabilities: {
41
+                'wdio:exclude': [
42
+                    ...defaultConfig.capabilities.participant2.capabilities['wdio:exclude'],
43
+                    ...ffExcludes
44
+                ]
45
+            }
46
+        },
47
+        participant3: {
48
+            capabilities: {
49
+                'wdio:exclude': [
50
+                    ...defaultConfig.capabilities.participant3.capabilities['wdio:exclude'],
51
+                    ...ffExcludes
52
+                ]
53
+            }
54
+        },
55
+        participant4: {
56
+            capabilities: {
57
+                'wdio:exclude': [
58
+                    ...defaultConfig.capabilities.participant4.capabilities['wdio:exclude'],
59
+                    ...ffExcludes
60
+                ]
61
+            }
37
         }
62
         }
38
     }
63
     }
39
 }, { clone: false });
64
 }, { clone: false });

+ 2
- 2
tests/wdio.grid.conf.ts Vedi File

1
 // wdio.grid.conf.ts
1
 // wdio.grid.conf.ts
2
 // extends the main configuration file to add the selenium grid address
2
 // extends the main configuration file to add the selenium grid address
3
-import { deepmerge } from 'deepmerge-ts';
3
+import { merge } from 'lodash-es';
4
 import { URL } from 'url';
4
 import { URL } from 'url';
5
 
5
 
6
 // @ts-ignore
6
 // @ts-ignore
9
 const gridUrl = new URL(process.env.GRID_HOST_URL as string);
9
 const gridUrl = new URL(process.env.GRID_HOST_URL as string);
10
 const protocol = gridUrl.protocol.replace(':', '');
10
 const protocol = gridUrl.protocol.replace(':', '');
11
 
11
 
12
-export const config = deepmerge(defaultConfig, {
12
+export const config = merge(defaultConfig, {
13
     protocol,
13
     protocol,
14
     hostname: gridUrl.hostname,
14
     hostname: gridUrl.hostname,
15
     port: gridUrl.port ? parseInt(gridUrl.port, 10) // Convert port to number
15
     port: gridUrl.port ? parseInt(gridUrl.port, 10) // Convert port to number

Loading…
Annulla
Salva