From 8dccbd14107bb6bd9c718ecf0b586a9267df64fd Mon Sep 17 00:00:00 2001 From: mgamis-msft Date: Tue, 16 Jul 2024 05:10:40 -0700 Subject: [PATCH] Automate dominant speaker, pinning, and spotlight tests --- .../src/components/Drawer/DrawerMenu.tsx | 1 + .../tests/browser/VideoGallery.spec.tsx | 160 +++++++++++++++++ .../browser/call/hermetic/Pinning.test.ts | 170 ++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 packages/react-components/tests/browser/VideoGallery.spec.tsx create mode 100644 packages/react-composites/tests/browser/call/hermetic/Pinning.test.ts diff --git a/packages/react-components/src/components/Drawer/DrawerMenu.tsx b/packages/react-components/src/components/Drawer/DrawerMenu.tsx index bc831b39975..ba72c69870b 100644 --- a/packages/react-components/src/components/Drawer/DrawerMenu.tsx +++ b/packages/react-components/src/components/Drawer/DrawerMenu.tsx @@ -107,6 +107,7 @@ export const _DrawerMenu = (props: _DrawerMenuProps): JSX.Element => { styles={props.styles?.drawerSurfaceStyles} onLightDismiss={props.onLightDismiss} heading={props.heading} + data-ui-id="drawer-menu" > {menuItemsToRender?.slice(0, 1).map((item) => diff --git a/packages/react-components/tests/browser/VideoGallery.spec.tsx b/packages/react-components/tests/browser/VideoGallery.spec.tsx new file mode 100644 index 00000000000..30bc9f085d1 --- /dev/null +++ b/packages/react-components/tests/browser/VideoGallery.spec.tsx @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react'; +import { test, expect } from '@playwright/experimental-ct-react'; +import { VideoGallery } from '../../src/components/VideoGallery'; +import { VideoGalleryLocalParticipant, VideoGalleryRemoteParticipant } from '../../src'; +import { Stack } from '@fluentui/react'; + +test.describe.only('VGL - VideoGallery tests', () => { + test.beforeEach(async ({ page }) => { + await page.evaluate(() => document.fonts.ready); + }); + + test('VideoGallery with only audio participants and dominant speakers', async ({ mount }) => { + const localParticipant: VideoGalleryLocalParticipant = { userId: 'test' }; + let remoteParticipants: VideoGalleryRemoteParticipant[] = Array.from({ length: 10 }, (_, i) => i + 1).map((i) => ({ + userId: `${i}`, + displayName: `${i}` + })); + const component = await mount( + + + + ); + await expect(component).toHaveScreenshot('VGL-1-1-videogallery-with-audio-only-before-dominant-speakers.png'); + await component.update( + + + + ); + await expect(component).toHaveScreenshot('VGL-1-2-videogallery-with-audio-only-after-dominant-speakers.png'); + }); + + test('VideoGallery with video participants and dominant speakers', async ({ mount }) => { + const localParticipant: VideoGalleryLocalParticipant = { userId: 'test' }; + const remoteParticipants: VideoGalleryRemoteParticipant[] = Array.from({ length: 10 }, (_, i) => i + 1).map( + (i) => ({ + userId: `${i}`, + displayName: `${i}` + }) + ); + // Assign video stream to some participants + remoteParticipants.find((p) => p.userId === '2')!.videoStream = { isAvailable: true }; + remoteParticipants.find((p) => p.userId === '3')!.videoStream = { isAvailable: true }; + remoteParticipants.find((p) => p.userId === '5')!.videoStream = { isAvailable: true }; + remoteParticipants.find((p) => p.userId === '7')!.videoStream = { isAvailable: true }; + remoteParticipants.find((p) => p.userId === '9')!.videoStream = { isAvailable: true }; + + const component = await mount( + + + + ); + await expect(component).toHaveScreenshot('VGL-2-1-videogallery-with-some-video-before-dominant-speakers.png'); + await component.update( + + + + ); + await expect(component).toHaveScreenshot('VGL-2-2-videogallery--with-some-video-after-dominant-speakers.png'); + }); + + test('VideoGallery with screen share on and dominant speakers', async ({ mount }) => { + const localParticipant: VideoGalleryLocalParticipant = { userId: 'test' }; + let remoteParticipants: VideoGalleryRemoteParticipant[] = Array.from({ length: 10 }, (_, i) => i + 1).map((i) => ({ + userId: `${i}`, + displayName: `${i}` + })); + remoteParticipants[5].isScreenSharingOn = true; + remoteParticipants[5].screenShareStream = { isAvailable: true }; + const component = await mount( + + + + ); + await expect(component).toHaveScreenshot('VGL-3-1-videogallery-with-screen-share-before-dominant-speakers.png'); + await component.update( + + + + ); + await expect(component).toHaveScreenshot('VGL-3-2-videogallery-with-screen-share-after-dominant-speakers.png'); + }); + + test('VideoGallery spotlight participant test', async ({ mount }) => { + const localParticipant: VideoGalleryLocalParticipant = { userId: 'test' }; + const remoteParticipants: VideoGalleryRemoteParticipant[] = Array.from({ length: 10 }, (_, i) => i + 1).map( + (i) => ({ userId: `${i}`, displayName: `${i}` }) + ); + const screenSharingParticipant: VideoGalleryRemoteParticipant = { + userId: '11', + displayName: '11' + }; + remoteParticipants.push(screenSharingParticipant); + const component = await mount( + + + + ); + await expect(component).toHaveScreenshot('VGL-4-1-videogallery-before-spotlight.png'); + + remoteParticipants.find((p) => p.userId === '8')!.spotlight = { spotlightedOrderPosition: 1 }; + component.update( + + + + ); + await expect(component).toHaveScreenshot('VGL-4-2-videogallery-after-spotlight.png'); + }); +}); + +const createMockVideoStream = (): HTMLElement => { + const mockVideoElement = document.createElement('div'); + mockVideoElement.style.width = '100%'; + mockVideoElement.style.height = '100%'; + mockVideoElement.style.textAlign = 'center'; + const imageElement = document.createElement('img'); + imageElement.src = 'images/screenshare-example.png'; + imageElement.style.maxWidth = decodeURIComponent('100%25'); + imageElement.style.maxHeight = decodeURIComponent('100%25'); + mockVideoElement.appendChild(imageElement); + return mockVideoElement as HTMLElement; +}; diff --git a/packages/react-composites/tests/browser/call/hermetic/Pinning.test.ts b/packages/react-composites/tests/browser/call/hermetic/Pinning.test.ts new file mode 100644 index 00000000000..0333c4f163c --- /dev/null +++ b/packages/react-composites/tests/browser/call/hermetic/Pinning.test.ts @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + addVideoStream, + buildUrlWithMockAdapter, + defaultMockCallAdapterState, + defaultMockRemoteParticipant, + test +} from './fixture'; +import { expect } from '@playwright/test'; +import { dataUiId, waitForSelector, stableScreenshot, isTestProfileMobile, pageClick } from '../../common/utils'; +import { IDS } from '../../common/constants'; + +const displayNames = [ + 'Tony Hawk', + 'Marie Curie', + 'Gal Gadot', + 'Margaret Atwood', + 'Kobe Bryant', + "Conan O'Brien", + 'Paul Bridges', + 'Fiona Harper', + 'Reina Takizawa', + 'Vasily Podkolzin', + 'Antonie van Leeuwenhoek', + 'Luciana Rodriguez' +]; + +test.describe.only('PIN - Pinning tests', async () => { + test('Pin and unpin remote participants via video tile', async ({ page, serverUrl }, testInfo) => { + const participants = displayNames.map((name) => defaultMockRemoteParticipant(name)); + addVideoStream(participants[1], true); + const initialState = defaultMockCallAdapterState(participants); + + await page.goto(buildUrlWithMockAdapter(serverUrl, initialState, { newControlBarExperience: 'true' })); + const videoGallery = await waitForSelector(page, dataUiId(IDS.videoGallery)); + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-1-1-pin-video-tile-before.png'); + + const isMobile = isTestProfileMobile(testInfo); + if (isMobile) { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=2`); + await videoTile.dispatchEvent('touchstart'); + await pageClick(page, 'div[role="menu"] >> text=Pin for me'); + } else { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=2`); + await videoTile.hover(); + const moreButton = await videoTile.waitForSelector(dataUiId(IDS.videoTileMoreOptionsButton)); + await moreButton.hover(); + await moreButton.click(); + await waitForSelector(page, dataUiId('video-tile-pin-participant-button')); + await pageClick(page, dataUiId('video-tile-pin-participant-button')); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-1-2-pin-video-tile-after.png'); + + if (isMobile) { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=1`); + await videoTile.dispatchEvent('touchstart'); + await page.waitForSelector(dataUiId('drawer-menu')); + } else { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=1`); + await videoTile.hover(); + const moreButton = await videoTile?.waitForSelector(dataUiId(IDS.videoTileMoreOptionsButton)); + await moreButton?.hover(); + await moreButton?.click(); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-1-3-unpin-video-tile-before.png'); + + if (isMobile) { + await pageClick(page, 'div[role="menu"] >> text=Unpin'); + } else { + await pageClick(page, dataUiId('video-tile-unpin-participant-button')); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-1-4-unpin-video-tile-after.png'); + }); + + test.only('Pin and unpin remote participants via participant item', async ({ page, serverUrl }, testInfo) => { + const participants = displayNames.map((name) => defaultMockRemoteParticipant(name)); + addVideoStream(participants[1], true); + const initialState = defaultMockCallAdapterState(participants); + + await page.goto(buildUrlWithMockAdapter(serverUrl, initialState, { newControlBarExperience: 'true' })); + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-2-1-pin-participant-item-before.png'); + + const isMobile = isTestProfileMobile(testInfo); + if (isMobile) { + await pageClick(page, dataUiId('common-call-composite-more-button')); + const drawerPeopleMenuDiv = await page.$('div[role="menu"] >> text=People'); + await drawerPeopleMenuDiv?.click(); + await pageClick(page, dataUiId('participant-item')); + await pageClick(page, 'div[role="menu"] >> text=Pin for me'); + await pageClick(page, 'button[aria-label="Back"]'); + } else { + await pageClick(page, dataUiId('common-call-composite-people-button')); + const participantItem = await page.waitForSelector(dataUiId('participant-item')); + await participantItem.hover(); + await pageClick(page, dataUiId(IDS.participantItemMenuButton)); + await pageClick(page, dataUiId('participant-item-pin-participant-button')); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-2-2-pin-participant-item-after.png'); + + if (isMobile) { + await pageClick(page, dataUiId('common-call-composite-more-button')); + const drawerPeopleMenuDiv = await page.$('div[role="menu"] >> text=People'); + await drawerPeopleMenuDiv?.click(); + await pageClick(page, dataUiId('participant-item')); + } else { + const participantItem = await page.waitForSelector(dataUiId('participant-item')); + await participantItem.hover(); + await pageClick(page, dataUiId(IDS.participantItemMenuButton)); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-2-3-unpin-participant-item-before.png'); + + if (isMobile) { + await pageClick(page, 'div[role="menu"] >> text=Unpin'); + await pageClick(page, 'button[aria-label="Back"]'); + } else { + await pageClick(page, dataUiId('participant-item-unpin-participant-button')); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-2-4-unpin-participant-item-after.png'); + }); + + test('Pin max remote participants', async ({ page, serverUrl }, testInfo) => { + const participants = displayNames.map((name) => defaultMockRemoteParticipant(name)); + addVideoStream(participants[1], true); + const initialState = defaultMockCallAdapterState(participants); + + await page.goto(buildUrlWithMockAdapter(serverUrl, initialState, { newControlBarExperience: 'true' })); + const videoGallery = await waitForSelector(page, dataUiId(IDS.videoGallery)); + + const isMobile = isTestProfileMobile(testInfo); + if (isMobile) { + for (let i = 0; i < 4; i++) { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=-1`); + await videoTile.dispatchEvent('touchstart'); + await pageClick(page, 'div[role="menu"] >> text=Pin for me'); + } + + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=-1`); + await videoTile.dispatchEvent('touchstart'); + await page.waitForSelector(dataUiId('drawer-menu')); + } else { + for (let i = 0; i < 4; i++) { + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=-1`); + await videoTile.hover(); + const moreButton = await videoTile.waitForSelector(dataUiId(IDS.videoTileMoreOptionsButton)); + await moreButton.hover(); + await moreButton.click(); + await waitForSelector(page, dataUiId('video-tile-pin-participant-button')); + await pageClick(page, dataUiId('video-tile-pin-participant-button')); + } + + const videoTile = await videoGallery.waitForSelector(dataUiId(IDS.videoTile) + ` >> nth=-1`); + await videoTile.hover(); + const moreButton = await videoTile?.waitForSelector(dataUiId(IDS.videoTileMoreOptionsButton)); + await moreButton?.hover(); + await moreButton?.click(); + } + + expect(await stableScreenshot(page)).toMatchSnapshot('PIN-3-1-pin-max-tiles.png'); + }); +});