diff --git a/openvidu-components-angular/e2e/panels.test.ts b/openvidu-components-angular/e2e/panels.test.ts index 08234d1a..95779e5a 100644 --- a/openvidu-components-angular/e2e/panels.test.ts +++ b/openvidu-components-angular/e2e/panels.test.ts @@ -29,39 +29,34 @@ describe('Panels: UI Navigation and Section Switching', () => { await browser.quit(); }); - /** - * TODO - * It only works with OpenVidu PRO because this is a PRO feature - */ - // it('should toggle BACKGROUND panel on prejoin page when VIDEO is MUTED', async () => { - // let element; - // await browser.get(`${url}`); - // element = await utils.waitForElement('#pre-join-container'); - // expect(await utils.isPresent('#pre-join-container')).toBeTrue(); + fit('should close BACKGROUND panel on prejoin page when VIDEO is MUTED', async () => { + let element; + await browser.get(`${url}`); + await utils.checkPrejoinIsPresent(); - // const backgroundButton = await utils.waitForElement('#background-effects-btn'); - // expect(await utils.isPresent('#background-effects-btn')).toBeTrue(); - // expect(await backgroundButton.isEnabled()).toBeTrue(); - // await backgroundButton.click(); - // await browser.sleep(500); + const backgroundButton = await utils.waitForElement('#background-effects-btn'); + expect(await utils.isPresent('#background-effects-btn')).toBeTrue(); + expect(await backgroundButton.isEnabled()).toBeTrue(); + await backgroundButton.click(); + await browser.sleep(500); - // await utils.waitForElement('#background-effects-container'); - // expect(await utils.isPresent('#background-effects-container')).toBeTrue(); + await utils.waitForElement('#background-effects-container'); + expect(await utils.isPresent('#background-effects-container')).toBeTrue(); - // element = await utils.waitForElement('#camera-button'); - // expect(await utils.isPresent('#camera-button')).toBeTrue(); - // expect(await element.isEnabled()).toBeTrue(); - // await element.click(); + element = await utils.waitForElement('#camera-button'); + expect(await utils.isPresent('#camera-button')).toBeTrue(); + expect(await element.isEnabled()).toBeTrue(); + await element.click(); - // await browser.sleep(500); - // element = await utils.waitForElement('#video-poster'); - // expect(await utils.isPresent('#video-poster')).toBeTrue(); + await browser.sleep(500); + element = await utils.waitForElement('#video-poster'); + expect(await utils.isPresent('#video-poster')).toBeTrue(); - // expect(await backgroundButton.isDisplayed()).toBeTrue(); - // expect(await backgroundButton.isEnabled()).toBeFalse(); + expect(await backgroundButton.isDisplayed()).toBeTrue(); + expect(await backgroundButton.isEnabled()).toBeFalse(); - // expect(await utils.isPresent('#background-effects-container')).toBeFalse(); - // }); + expect(await utils.isPresent('#background-effects-container')).toBeFalse(); + }); it('should open and close the CHAT panel and verify its content', async () => { await browser.get(`${url}&prejoin=false`); diff --git a/openvidu-components-angular/e2e/utils.po.test.ts b/openvidu-components-angular/e2e/utils.po.test.ts index 9571e3f0..fcc93935 100644 --- a/openvidu-components-angular/e2e/utils.po.test.ts +++ b/openvidu-components-angular/e2e/utils.po.test.ts @@ -1,4 +1,8 @@ import { By, until, WebDriver, WebElement } from 'selenium-webdriver'; +import * as fs from 'fs'; +import { PNG } from 'pngjs'; +import pixelmatch from 'pixelmatch'; +type PNGWithMetadata = PNG & { data: Buffer }; export class OpenViduComponentsPO { private TIMEOUT = 10 * 1000; @@ -188,6 +192,16 @@ export class OpenViduComponentsPO { await this.waitForElement('#participants-panel-btn'); await this.clickOn('#participants-panel-btn'); break; + case 'backgrounds': + await this.waitForElement('#more-options-btn'); + await this.clickOn('#more-options-btn'); + + await this.browser.sleep(500); + await this.waitForElement('#virtual-bg-btn'); + await this.clickOn('#virtual-bg-btn'); + + await this.browser.sleep(1000); + break; case 'settings': await this.toggleToolbarMoreOptions(); @@ -198,4 +212,71 @@ export class OpenViduComponentsPO { await this.browser.sleep(500); } + + async applyBackground(bgId: string) { + await this.waitForElement('ov-background-effects-panel'); + await this.browser.sleep(1000); + await this.waitForElement(`#effect-${bgId}`); + + await this.clickOn(`#effect-${bgId}`); + await this.browser.sleep(2000); + } + + async applyVirtualBackgroundFromPrejoin(bgId: string): Promise { + await this.waitForElement('#backgrounds-button'); + await this.clickOn('#backgrounds-button'); + + await this.applyBackground(bgId); + await this.clickOn('#backgrounds-button'); + } + + async saveScreenshot(filename: string, element: WebElement) { + const image = await element.takeScreenshot(); + fs.writeFileSync(filename, image, 'base64'); + } + + async expectVirtualBackgroundApplied( + img1Name: string, + img2Name: string, + { + threshold = 0.4, + minDiffPixels = 500, + debug = false + }: { + threshold?: number; + minDiffPixels?: number; + debug?: boolean; + } = {} + ): Promise { + const beforeImg = PNG.sync.read(fs.readFileSync(img1Name)); + const afterImg = PNG.sync.read(fs.readFileSync(img2Name)); + const { width, height } = beforeImg; + const diff = new PNG({ width, height }); + + // const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, { + // threshold: 0.4 + // // alpha: 0.5, + // // includeAA: false, + // // diffColor: [255, 0, 0] + // }); + + const numDiffPixels = pixelmatch(beforeImg.data, afterImg.data, diff.data, width, height, { + threshold + // includeAA: true + }); + + if (numDiffPixels <= minDiffPixels) { + // Sólo guardar los archivos de debug si falla la prueba + if (debug) { + fs.writeFileSync('before.png', PNG.sync.write(beforeImg)); + fs.writeFileSync('after.png', PNG.sync.write(afterImg)); + fs.writeFileSync('diff.png', PNG.sync.write(diff)); + } + } + + expect(numDiffPixels).toBeGreaterThan(minDiffPixels, 'The virtual background was not applied correctly'); + + // fs.writeFileSync('diff.png', PNG.sync.write(diff)); + // expect(numDiffPixels).to.be.greaterThan(500, 'The virtual background was not applied correctly'); + } } diff --git a/openvidu-components-angular/e2e/virtual-backgrounds.test.ts b/openvidu-components-angular/e2e/virtual-backgrounds.test.ts new file mode 100644 index 00000000..8b3a55a5 --- /dev/null +++ b/openvidu-components-angular/e2e/virtual-backgrounds.test.ts @@ -0,0 +1,155 @@ +import { Builder, WebDriver } from 'selenium-webdriver'; +import { TestAppConfig } from './selenium.conf'; +import { OpenViduComponentsPO } from './utils.po.test'; + +const url = TestAppConfig.appUrl; + +describe('Prejoin: Virtual Backgrounds', () => { + let browser: WebDriver; + let utils: OpenViduComponentsPO; + + async function createChromeBrowser(): Promise { + return await new Builder() + .forBrowser(TestAppConfig.browserName) + .withCapabilities(TestAppConfig.browserCapabilities) + .setChromeOptions(TestAppConfig.browserOptions) + .usingServer(TestAppConfig.seleniumAddress) + .build(); + } + + beforeEach(async () => { + browser = await createChromeBrowser(); + utils = new OpenViduComponentsPO(browser); + }); + + afterEach(async () => { + try { + await utils.leaveRoom(); + } catch (error) {} + await browser.quit(); + }); + + it('should close BACKGROUNDS on prejoin page when VIDEO is disabled', async () => { + let element; + await browser.get(`${url}`); + await utils.checkPrejoinIsPresent(); + + const backgroundButton = await utils.waitForElement('#backgrounds-button'); + expect(await utils.isPresent('#backgrounds-button')).toBeTrue(); + expect(await backgroundButton.isEnabled()).toBeTrue(); + await utils.clickOn('#backgrounds-button'); + await browser.sleep(500); + + await utils.waitForElement('#background-effects-container'); + expect(await utils.isPresent('#background-effects-container')).toBeTrue(); + + await utils.clickOn('#camera-button'); + + await browser.sleep(500); + element = await utils.waitForElement('#video-poster'); + expect(await utils.isPresent('#video-poster')).toBeTrue(); + + expect(await backgroundButton.isDisplayed()).toBeTrue(); + expect(await backgroundButton.isEnabled()).toBeFalse(); + + await browser.sleep(1000); + expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0); + }); + + it('should open and close BACKGROUNDS panel on prejoin page', async () => { + await browser.get(`${url}`); + await utils.checkPrejoinIsPresent(); + + const backgroundButton = await utils.waitForElement('#backgrounds-button'); + expect(await utils.isPresent('#backgrounds-button')).toBeTrue(); + expect(await backgroundButton.isEnabled()).toBeTrue(); + await backgroundButton.click(); + await browser.sleep(500); + + await utils.waitForElement('#background-effects-container'); + expect(await utils.isPresent('#background-effects-container')).toBeTrue(); + + await utils.clickOn('#backgrounds-button'); + await browser.sleep(1000); + expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0); + }); + + it('should apply a background effect on prejoin page', async () => { + await browser.get(`${url}`); + await utils.checkPrejoinIsPresent(); + + let videoElement = await utils.waitForElement('.OV_video-element'); + await utils.saveScreenshot('before.png', videoElement); + + await utils.applyVirtualBackgroundFromPrejoin('1'); + + await browser.sleep(1000); + + videoElement = await utils.waitForElement('.OV_video-element'); + await utils.saveScreenshot('after.png', videoElement); + + await utils.expectVirtualBackgroundApplied('before.png', 'after.png'); + }); +}); + +describe('Room: Virtual Backgrounds', () => { + let browser: WebDriver; + let utils: OpenViduComponentsPO; + + async function createChromeBrowser(): Promise { + return await new Builder() + .forBrowser(TestAppConfig.browserName) + .withCapabilities(TestAppConfig.browserCapabilities) + .setChromeOptions(TestAppConfig.browserOptions) + .usingServer(TestAppConfig.seleniumAddress) + .build(); + } + + beforeEach(async () => { + browser = await createChromeBrowser(); + utils = new OpenViduComponentsPO(browser); + }); + + afterEach(async () => { + try { + await utils.leaveRoom(); + } catch (error) {} + await browser.quit(); + }); + + it('should open and close BACKGROUNDS panel in the room', async () => { + await browser.get(`${url}&prejoin=false`); + await utils.checkLayoutPresent(); + await utils.checkToolbarIsPresent(); + await utils.togglePanel('backgrounds'); + + await utils.waitForElement('#background-effects-container'); + expect(await utils.isPresent('#background-effects-container')).toBeTrue(); + + await utils.togglePanel('backgrounds'); + await browser.sleep(1000); + expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0); + }); + + it('should apply a background effect in the room', async () => { + await browser.get(`${url}&prejoin=false`); + await utils.checkLayoutPresent(); + + await utils.togglePanel('backgrounds'); + + await utils.waitForElement('#background-effects-container'); + expect(await utils.isPresent('#background-effects-container')).toBeTrue(); + + let videoElement = await utils.waitForElement('.OV_video-element'); + await utils.saveScreenshot('before.png', videoElement); + + await utils.applyBackground('1'); + + await browser.sleep(1000); + + videoElement = await utils.waitForElement('.OV_video-element'); + await utils.saveScreenshot('after.png', videoElement); + + await utils.expectVirtualBackgroundApplied('before.png', 'after.png'); + }); +}); diff --git a/openvidu-components-angular/package-lock.json b/openvidu-components-angular/package-lock.json index 7a373c9d..3eae5af7 100644 --- a/openvidu-components-angular/package-lock.json +++ b/openvidu-components-angular/package-lock.json @@ -33,6 +33,7 @@ "@compodoc/compodoc": "^1.1.25", "@types/jasmine": "^5.1.4", "@types/node": "20.12.14", + "@types/pngjs": "^6.0.5", "@types/selenium-webdriver": "4.1.16", "@types/ws": "^8.5.12", "chromedriver": "138.0.0", @@ -57,6 +58,8 @@ "lint-staged": "^15.2.10", "ng-packagr": "19.2.2", "npm-watch": "^0.13.0", + "pixelmatch": "^7.1.0", + "pngjs": "^7.0.0", "prettier": "3.3.3", "selenium-webdriver": "4.32.0", "ts-node": "10.9.2", @@ -2937,33 +2940,6 @@ } } }, - "node_modules/@compodoc/compodoc/node_modules/@angular-devkit/schematics/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/core": { "version": "7.25.8", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", @@ -3136,21 +3112,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@compodoc/compodoc/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/@compodoc/compodoc/node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -3161,36 +3122,6 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/@compodoc/compodoc/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/@compodoc/compodoc/node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@compodoc/live-server": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@compodoc/live-server/-/live-server-1.2.3.tgz", @@ -6659,6 +6590,16 @@ "@types/node": "*" } }, + "node_modules/@types/pngjs": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz", + "integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -15821,6 +15762,19 @@ "@napi-rs/nice": "^1.0.1" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -15925,6 +15879,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/portfinder": { "version": "1.0.37", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", diff --git a/openvidu-components-angular/package.json b/openvidu-components-angular/package.json index 211807ef..3423492c 100644 --- a/openvidu-components-angular/package.json +++ b/openvidu-components-angular/package.json @@ -25,6 +25,7 @@ "@compodoc/compodoc": "^1.1.25", "@types/jasmine": "^5.1.4", "@types/node": "20.12.14", + "@types/pngjs": "^6.0.5", "@types/selenium-webdriver": "4.1.16", "@types/ws": "^8.5.12", "chromedriver": "138.0.0", @@ -49,6 +50,8 @@ "lint-staged": "^15.2.10", "ng-packagr": "19.2.2", "npm-watch": "^0.13.0", + "pixelmatch": "^7.1.0", + "pngjs": "^7.0.0", "prettier": "3.3.3", "selenium-webdriver": "4.32.0", "ts-node": "10.9.2", @@ -84,19 +87,20 @@ "lib:pack": "cd ./dist/openvidu-components-angular && npm pack", "lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call/frontend", "lib:test": "ng test openvidu-components-angular --no-watch --code-coverage", - "e2e:nested-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/*.test.js", + "e2e:nested-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/*.test.js", "e2e:nested-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/events.test.js", - "e2e:nested-structural-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/structural-directives.test.js", - "e2e:nested-attribute-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/attribute-directives.test.js", + "e2e:nested-structural-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/structural-directives.test.js", + "e2e:nested-attribute-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/attribute-directives.test.js", "e2e:lib-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/api-directives.test.js", "e2e:lib-internal-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/internal-directives.test.js", - "e2e:lib-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/chat.test.js", + "e2e:lib-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/chat.test.js", "e2e:lib-events": "tsc --project ./e2e && npx jasmine ./e2e/dist/events.test.js", - "e2e:lib-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/media-devices.test.js", - "e2e:lib-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/panels.test.js", - "e2e:lib-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/screensharing.test.js", - "e2e:lib-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/stream.test.js", - "e2e:lib-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/toolbar.test.js", + "e2e:lib-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/media-devices.test.js", + "e2e:lib-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/panels.test.js", + "e2e:lib-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/screensharing.test.js", + "e2e:lib-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/stream.test.js", + "e2e:lib-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/toolbar.test.js", + "e2e:lib-virtual-backgrounds": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/virtual-backgrounds.test.js", "simulate:multiparty": "livekit-cli load-test --url ws://localhost:7880 --api-key devkey --api-secret secret --room daily-call --publishers 8 --audio-publishers 8 --identity-prefix Participant --identity publisher", "husky": "cd .. && husky install" },