diff --git a/frontend/webcomponent/playwright.config.ts b/frontend/webcomponent/playwright.config.ts index 7f449f6..e19b186 100644 --- a/frontend/webcomponent/playwright.config.ts +++ b/frontend/webcomponent/playwright.config.ts @@ -2,12 +2,16 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ - testDir: './tests/e2e', - timeout: 30000, - retries: 1, - use: { - headless: true, - viewport: { width: 1280, height: 720 }, - ignoreHTTPSErrors: true - } + testDir: './tests/e2e', + timeout: 30000, + retries: 0, + workers: 1, + fullyParallel: false, + use: { + headless: false, + viewport: { width: 1280, height: 720 }, + ignoreHTTPSErrors: true, + permissions: ['camera', 'microphone'], + video: 'retain-on-failure' + } }); diff --git a/frontend/webcomponent/tests/e2e/openvidu-meet.test.ts b/frontend/webcomponent/tests/e2e/openvidu-meet.test.ts index d4c7190..f90b762 100644 --- a/frontend/webcomponent/tests/e2e/openvidu-meet.test.ts +++ b/frontend/webcomponent/tests/e2e/openvidu-meet.test.ts @@ -1,30 +1,128 @@ -// tests/e2e/openvidu-meet.e2e.ts -import { test, expect } from '@playwright/test'; +import { test, expect, BrowserContext, Browser, Page, chromium } from '@playwright/test'; +import { waitForElementInIframe } from '../helpers/function-helpers'; +import fs from 'fs'; -test.describe('OpenViduMeet E2E Tests', () => { - test('should load iframe with correct URL including additional parameters', async ({ page }) => { - await page.setContent( - `` - ); - const iframe = page.locator('iframe'); - await expect(iframe).toHaveAttribute('src', 'https://meet.example.com?room-name=Sala1&pepito-perez=55'); +test.describe('Web Component E2E Tests', () => { + const testAppUrl = 'http://localhost:5080'; + const testRoomPrefix = 'test-room'; + + let browser: Browser; + let context: BrowserContext; + let page: Page; + + test.beforeAll(async () => { + // Create a test room before all tests + const tempBrowser = await chromium.launch(); + const tempContext = await tempBrowser.newContext(); + const tempPage = await tempContext.newPage(); + await tempPage.goto(testAppUrl); + await tempPage.waitForSelector('.create-room'); + await tempPage.fill('#room-id-prefix', testRoomPrefix); + await tempPage.click('.create-room-btn'); + await tempPage.waitForSelector(`#${testRoomPrefix}`); + await tempBrowser.close(); }); - test('should handle postMessage interactions', async ({ page }) => { - await page.setContent(``); + test.beforeEach(async () => { + browser = await chromium.launch({ headless: false }); + const storageState = fs.existsSync('test_localstorage_state.json') + ? { storageState: 'test_localstorage_state.json' } + : {}; + context = await browser.newContext(storageState); + page = await context.newPage(); + await page.goto(testAppUrl); + await page.waitForSelector('.rooms-container'); + await page.waitForSelector(`#${testRoomPrefix}`); + await page.click('.dropdown-button'); + await page.waitForSelector('#join-as-moderator'); + await page.waitForSelector('#join-as-publisher'); + }); - const [event] = await Promise.all([ - page.evaluate(() => { - return new Promise((resolve) => { - const component = document.querySelector('openvidu-meet'); - if (component) { - component.addEventListener('conference-event', (e) => resolve(e.detail)); - } - window.postMessage({ event: 'participant-joined', participant: 'María Gómez' }, '*'); - }); - }) - ]); + test.afterEach(async () => { + await context.storageState({ path: 'test_localstorage_state.json' }); + await browser.close(); + }); - expect(event).toEqual({ event: 'participant-joined', participant: 'María Gómez' }); + test.describe('Component Rendering', () => { + test('should load the web component with proper iframe', async () => { + await page.click('#join-as-moderator'); + const component = page.locator('openvidu-meet'); + await expect(component).toBeVisible(); + + const hasIframe = await page.evaluate(() => { + const component = document.querySelector('openvidu-meet'); + return !!component?.shadowRoot?.querySelector('iframe'); + }); + expect(hasIframe).toBeTruthy(); + }); + }); + + test.describe('Event Handling', () => { + test('should successfully join as moderator and receive JOIN event', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + await page.waitForSelector('.event-JOIN'); + const joinElements = await page.locator('.event-JOIN').all(); + expect(joinElements.length).toBe(1); + }); + + test('should successfully join as publisher and receive JOIN event', async () => { + await page.click('#join-as-publisher'); + await waitForElementInIframe(page, 'ov-session'); + await page.waitForSelector('.event-JOIN'); + const joinElements = await page.locator('.event-JOIN').all(); + expect(joinElements.length).toBe(1); + }); + + test('should successfully join to room and receive LEFT event when using leave command', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + await page.click('#leave-room-btn'); + await page.waitForSelector('.event-LEFT'); + const leftElements = await page.locator('.event-LEFT').all(); + expect(leftElements.length).toBe(1); + }); + + test('should successfully join to room and receive LEFT event when using disconnect button', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + const button = await waitForElementInIframe(page, '#leave-btn'); + await button.click(); + await page.waitForSelector('.event-LEFT'); + const leftElements = await page.locator('.event-LEFT').all(); + expect(leftElements.length).toBe(1); + }); + + test('should successfully join to room and receive MEETING_ENDED event when using end meeting command', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + await page.click('#end-meeting-btn'); + await page.waitForSelector('.event-MEETING_ENDED'); + const meetingEndedElements = await page.locator('.event-MEETING_ENDED').all(); + expect(meetingEndedElements.length).toBe(1); + + // Check LEFT event does not exist + const leftEventElements = await page.locator('.event-LEFT').all(); + expect(leftEventElements.length).toBe(0); + }); + }); + + test.describe('Webhook Handling', () => { + test('should successfully join to room and receive meetingStarted webhook', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + await page.waitForSelector('.webhook-meetingStarted'); + const meetingStartedElements = await page.locator('.webhook-meetingStarted').all(); + expect(meetingStartedElements.length).toBe(1); + }); + + test('should successfully join to room and receive meetingEnded webhook', async () => { + await page.click('#join-as-moderator'); + await waitForElementInIframe(page, 'ov-session'); + await page.click('#end-meeting-btn'); + await page.waitForSelector('.webhook-meetingEnded'); + const meetingEndedElements = await page.locator('.webhook-meetingEnded').all(); + expect(meetingEndedElements.length).toBe(1); + }); }); }); diff --git a/frontend/webcomponent/tests/helpers/function-helpers.ts b/frontend/webcomponent/tests/helpers/function-helpers.ts new file mode 100644 index 0000000..3dc7bd8 --- /dev/null +++ b/frontend/webcomponent/tests/helpers/function-helpers.ts @@ -0,0 +1,56 @@ +import { Page, Locator, FrameLocator } from '@playwright/test'; + +/** + * Gets a FrameLocator for an iframe inside a Shadow DOM + * @param page - Playwright page object + * @param componentSelector - Selector for the web component with Shadow DOM + * @param iframeSelector - Selector for the iframe within the Shadow DOM + * @returns FrameLocator that can be used to access iframe contents + */ +export async function getIframeInShadowDom( + page: Page, + componentSelector: string = 'openvidu-meet', + iframeSelector: string = 'iframe' +): Promise { + // Verify the component exists + await page.waitForSelector(componentSelector); + + // Use frameLocator to access the iframe contents + return page.frameLocator(`${componentSelector} >>> ${iframeSelector}`); +} + +/** + * Waits for an element inside an iframe within Shadow DOM + * @param page - Playwright page object + * @param elementSelector - Selector for the element inside the iframe + * @param options - Optional configuration + * @returns Locator for the found element + */ +export async function waitForElementInIframe( + page: Page, + elementSelector: string, + options: { + componentSelector?: string; + iframeSelector?: string; + timeout?: number; + state?: 'attached' | 'detached' | 'visible' | 'hidden'; + } = {} +): Promise { + const { + componentSelector = 'openvidu-meet', + iframeSelector = 'iframe', + timeout = 30000, + state = 'visible' + } = options; + + // Get the iframe + const frameLocator = await getIframeInShadowDom(page, componentSelector, iframeSelector); + + // Get element locator + const elementLocator = frameLocator.locator(elementSelector); + + // Wait for the element with the specified state + await elementLocator.waitFor({ state, timeout }); + + return elementLocator; +}