diff --git a/apps/webapp/src/script/components/Meeting/meetingNotificationHost/meetingNotificationHost.tsx b/apps/webapp/src/script/components/Meeting/meetingNotificationHost/meetingNotificationHost.tsx index dd3f77be2cb..915e19daa66 100644 --- a/apps/webapp/src/script/components/Meeting/meetingNotificationHost/meetingNotificationHost.tsx +++ b/apps/webapp/src/script/components/Meeting/meetingNotificationHost/meetingNotificationHost.tsx @@ -90,6 +90,7 @@ export const MeetingNotificationHost = ({isStandalone}: MeetingNotificationHostP css={meetingNotificationHostButtonStyles} aria-expanded={isExpanded} aria-controls="meeting-notification-list" + data-uie-name="meeting-notification-expand" onClick={() => useMeetingNotificationStore.getState().setIsExpanded(!isExpanded)} > diff --git a/apps/webapp/test/e2e_tests/backend/brigRepository.e2e.ts b/apps/webapp/test/e2e_tests/backend/brigRepository.e2e.ts index d36ec986208..6abc357fafa 100644 --- a/apps/webapp/test/e2e_tests/backend/brigRepository.e2e.ts +++ b/apps/webapp/test/e2e_tests/backend/brigRepository.e2e.ts @@ -80,6 +80,16 @@ export class BrigRepositoryE2E { }); } + public async enableMeetingsFeature(teamId: string) { + await this.axiosInstance.patch(`i/teams/${teamId}/features/meetings`, { + status: 'enabled', + }); + } + + public async unlockMeetingsFeature(teamId: string) { + await this.axiosInstance.put(`i/teams/${teamId}/features/meetings/unlocked`, {}); + } + public async configureMLSFeature( teamId: string, config: { diff --git a/apps/webapp/test/e2e_tests/specs/Meetings/meetings.spec.ts b/apps/webapp/test/e2e_tests/specs/Meetings/meetings.spec.ts new file mode 100644 index 00000000000..7aa1251aebc --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/Meetings/meetings.spec.ts @@ -0,0 +1,67 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +import type {Page} from '@playwright/test'; + +import {PageManager} from 'test/e2e_tests/pageManager'; +import {expect, LOGIN_TIMEOUT, test} from 'test/e2e_tests/test.fixtures'; + +const loginWithMeetingsEnabled = (user: {email: string; password: string}) => async (page: Page) => { + const pageManager = PageManager.from(page); + const {pages, components} = pageManager.webapp; + await pageManager.openLoginPage(); + await pages.login().login(user); + await components.conversationSidebar().sidebar.waitFor({state: 'visible', timeout: LOGIN_TIMEOUT}); + const clientUrl = new URL('/?enabled-features=meetings#/clients', page.url()); + await page.goto(clientUrl.href, {waitUntil: 'domcontentloaded'}); + await expect.poll(() => new URL(page.url()).searchParams.get('enabled-features')).toBe('meetings'); + await components.conversationSidebar().sidebar.waitFor({state: 'visible', timeout: LOGIN_TIMEOUT}); +}; + +test('single participant meeting notification', async ({createUser, createTeam, createPage}) => { + const member = await createUser(); + const team = await createTeam('Meetings', {users: [member], features: {meetings: true}}); + const owner = team.owner; + + const [ownerPage, memberPage] = await Promise.all([ + createPage(loginWithMeetingsEnabled(owner)), + createPage(loginWithMeetingsEnabled(member)), + ]); + + await ownerPage.locator('[data-uie-name="go-meetings"]').click(); + await ownerPage.locator('[data-uie-name="schedule-meeting"]').click(); + + const modal = ownerPage.locator('[data-uie-name="schedule-meeting-modal"]'); + await modal.locator('[data-uie-name="schedule-meeting-title"]').fill('Single participant meeting'); + + const participants = modal.locator('[data-uie-name="schedule-meeting-participants"]'); + await participants.locator('[data-uie-name="schedule-meeting-participants-input"]').fill(member.fullName); + await modal.locator('[data-uie-name="item-user"]').filter({hasText: member.fullName}).click(); + + await modal.locator('[data-uie-name="schedule-meeting-modal-submit"]').click(); + await expect(modal).toBeHidden(); + + const host = memberPage.locator('[data-uie-name="meeting-notification-host"]'); + await expect.poll(() => host.count(), {timeout: 30_000}).toBe(1); + await expect(host).toBeVisible(); + await host.getByTestId('meeting-notification-expand').click(); + + const cards = host.locator('[data-uie-name^="meeting-notification-card-"]'); + await expect(cards).toHaveCount(1); + await expect(cards).toContainText('Update: Single participant meeting'); +}); diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts index 004cd4ef3af..ae893d05e0b 100644 --- a/apps/webapp/test/e2e_tests/test.fixtures.ts +++ b/apps/webapp/test/e2e_tests/test.fixtures.ts @@ -203,6 +203,7 @@ export const createTeam = async ( features?: { conferenceCalling?: boolean; channels?: boolean; + meetings?: boolean; mls?: boolean | Parameters[1]; cells?: boolean; }; @@ -261,6 +262,12 @@ export const createTeam = async ( await api.waitForFeatureToBeEnabled(FEATURE_KEY.CHANNELS, teamId, owner.token); } + if (options.features.meetings) { + await api.brig.unlockMeetingsFeature(teamId); + await api.brig.enableMeetingsFeature(teamId); + await api.waitForFeatureToBeEnabled(FEATURE_KEY.MEETINGS, teamId, owner.token); + } + if (options.features.cells) { await api.brig.unlockCellsFeature(teamId); await api.brig.enableCells(teamId);