Compare commits

..

No commits in common. "master" and "v3.0.0-beta2" have entirely different histories.

359 changed files with 28107 additions and 65306 deletions

113
.github/workflows/openvidu-ce-test.yml vendored Normal file
View File

@ -0,0 +1,113 @@
name: OpenVidu CE Tests
on:
push:
branches:
- master
paths-ignore:
- ".github/workflows/openvidu-components-angular-E2E.yml"
- "openvidu-components-angular/**"
- "openvidu-server/docker/**"
- "openvidu-server/deployments/**"
pull_request:
branches:
- master
workflow_dispatch:
inputs:
TEST_IMAGE:
description: "Docker image where to run the tests"
required: true
default: "openvidu/openvidu-test-e2e:22.04"
KURENTO_JAVA_COMMIT:
description: 'Commit to use in kurento-java dependencies. If "default" the release version declared in property "version.kurento" of openvidu-parent/pom.xml will be used'
required: true
default: "default"
KURENTO_MEDIA_SERVER_IMAGE:
description: "Docker image of kurento-media-server"
required: true
default: "kurento/kurento-media-server:7.0.1"
DOCKER_RECORDING_VERSION:
description: "Force version of openvidu/openvidu-recording container"
required: true
default: "default"
CHROME_VERSION:
description: "Version of Chrome to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-chrome/tags"
required: true
default: "latest"
FIREFOX_VERSION:
description: "Version of Firefox to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-firefox/tags"
required: true
default: "latest"
EDGE_VERSION:
description: "Version of Edge to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-edge/tags"
required: true
default: "latest"
jobs:
main:
runs-on: ubuntu-latest
container:
image: ${{ inputs.TEST_IMAGE || 'openvidu/openvidu-test-e2e:22.04' }}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/openvidu:/opt/openvidu
env:
TEST_IMAGE: ${{ inputs.TEST_IMAGE || 'openvidu/openvidu-test-e2e:22.04' }}
KURENTO_SNAPSHOTS_URL: ${{ secrets.KURENTO_SNAPSHOTS_URL }}
KURENTO_MEDIA_SERVER_IMAGE: ${{ inputs.KURENTO_MEDIA_SERVER_IMAGE || 'kurento/kurento-media-server:7.0.1' }}
KURENTO_JAVA_COMMIT: ${{ inputs.KURENTO_JAVA_COMMIT || 'default' }}
DOCKER_RECORDING_VERSION: ${{ inputs.DOCKER_RECORDING_VERSION || 'default' }}
CHROME_VERSION: ${{ inputs.CHROME_VERSION || 'latest' }}
FIREFOX_VERSION: ${{ inputs.FIREFOX_VERSION || 'latest' }}
EDGE_VERSION: ${{ inputs.EDGE_VERSION || 'latest' }}
steps:
- uses: actions/checkout@v3
- name: Setup scripts
run: |
curl -sOJ --output-dir /opt https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/commons/build.sh
curl -sOJ --output-dir /opt https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/commons/test-utils.sh
cp ci-scripts/openvidu-e2e-tests.sh /opt/openvidu-e2e-tests.sh
find /opt/*.sh -type f -print0 | xargs -0 chmod u+x
- name: Clean environment
run: /opt/build.sh --clean-environment
- name: Prepare test environment
run: /opt/test-utils.sh --prepare-test-environment "${TEST_IMAGE}"
- name: Check and prepare kurento snapshots
run: /opt/build.sh --check-and-prepare-kurento-snapshot
- name: Use specific kurento-java commit
if: ${{ env.KURENTO_JAVA_COMMIT != 'default'}}
run: /opt/test-utils.sh --use-specific-kurento-java-commit
- name: Build openvidu-browser
run: /opt/build.sh --build-openvidu-browser
- name: Build openvidu-node-client
run: /opt/build.sh --build-openvidu-node-client
- name: Build openvidu-java-client
run: /opt/build.sh --build-openvidu-java-client
- name: Build openvidu-parent
run: /opt/build.sh --build-openvidu-parent
- name: Build openvidu-testapp
run: /opt/build.sh --build-openvidu-testapp
- name: Build openvidu-server dashboard
run: /opt/build.sh --build-openvidu-server-dashboard true
- name: Build openvidu-server
run: /opt/build.sh --build-openvidu-server
- name: openvidu-server unit tests
run: /opt/openvidu-e2e-tests.sh --openvidu-server-unit-tests
- name: openvidu-server integration tests
run: /opt/openvidu-e2e-tests.sh --openvidu-server-integration-tests
- name: Environment launch Kurento
run: /opt/openvidu-e2e-tests.sh --environment-launch-kurento
- name: Serve openvidu-testapp
run: /opt/test-utils.sh --serve-openvidu-testapp
- name: OpenVidu E2E Tests Kurento
run: /opt/openvidu-e2e-tests.sh --openvidu-e2e-tests-kurento
- name: Test reports
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: "**/target/surefire-reports/TEST-*.xml"
- name: Upload logs
uses: actions/upload-artifact@v3
if: always() # always run even if the previous step fails
with:
name: Logs
path: |
/opt/openvidu/*.log

View File

@ -0,0 +1,158 @@
name: openvidu-components-angular E2E
on:
push:
paths:
- 'openvidu-components-angular/**'
- 'openvidu-browser/**'
- 'openvidu-node-client/**'
- '.github/workflows/openvidu-components-angular-E2E.yml'
pull_request:
branches:
- master
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA'
required: false
default: ''
jobs:
test_setup:
name: Test setup
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Commit URL
run: echo https://github.com/OpenVidu/openvidu/commit/${{ inputs.commit_sha || github.sha }}
- name: Send repository dispatch event
env:
GITHUB_TOKEN: ${{ secrets.OPENVIDU_DISPATCH_EVENT_GA }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message || 'Manually' }}
COMMIT_URL: ${{ github.event.commits[0].url || 'Manually' }}
BRANCH_NAME: ${{ github.ref_name }}
run: |
curl \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'", "branch-name":"'"$BRANCH_NAME"'"}}'
- name: Build openvidu-browser
run: |
cd openvidu-browser
npm install
npm run build && \
npm pack
- uses: actions/upload-artifact@v4
with:
name: openvidu-browser
path: openvidu-browser/openvidu-browser-*.tgz
openvidu_angular_e2e:
needs: test_setup
name: OpenVidu Angular E2E tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: actions/download-artifact@v4
with:
name: openvidu-browser
path: openvidu-components-angular
- name: Run Browserless Chrome
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run openvidu-server-kms
run: |
docker run -p 4443:4443 --rm -d \
-e OPENVIDU_SECRET=MY_SECRET \
openvidu/openvidu-dev:latest
- name: Install openvidu-browser and dependencies
run: |
cd openvidu-components-angular
npm install openvidu-browser-*.tgz
- name: Build openvidu-angular
run: npm run lib:build --prefix openvidu-components-angular
- name: Build openvidu-angular-testapp
run: npm run build --prefix openvidu-components-angular
- name: Serve openvidu-angular-testapp
run: npm run start-prod --prefix openvidu-components-angular &
- name: Run openvidu-angular E2E
run: npm run lib:e2e-ci --prefix openvidu-components-angular
webcomponent_e2e:
needs: test_setup
name: Webcomponent E2E CE tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: actions/download-artifact@v4
with:
name: openvidu-browser
path: openvidu-components-angular
- name: Run Browserless Chrome
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run openvidu-server-kms
run: |
docker run -p 4443:4443 --rm -d \
-e OPENVIDU_SECRET=MY_SECRET \
openvidu/openvidu-dev:latest
- name: Install openvidu-browser and dependencies
run: |
cd openvidu-components-angular
npm install openvidu-browser-*.tgz
- name: Build openvidu-angular
run: npm run lib:build --prefix openvidu-components-angular
- name: Build openvidu-webcomponent
run: npm run webcomponent:build --prefix openvidu-components-angular
- name: Serve Webcomponent Testapp
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
- name: Run Webcomponent E2E
run: npm run webcomponent:e2e-ci --prefix openvidu-components-angular
webcomponent_e2e_pro:
if: false #Skip PRO test because infra is unstable
needs: test_setup
name: Webcomponent E2E PRO tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- uses: actions/download-artifact@v4
with:
name: openvidu-browser
path: openvidu-components-angular
- name: Run Browserless Chrome
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Install openvidu-browser and dependencies
run: |
cd openvidu-components-angular
npm install openvidu-browser-*.tgz
- name: Build openvidu-angular
run: npm run lib:build --prefix openvidu-components-angular
- name: Build openvidu-webcomponent
run: npm run webcomponent:build --prefix openvidu-components-angular
- name: Serve Webcomponent Testapp
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
- name: Run Webcomponent E2E PRO
env:
OPENVIDU_SERVER_URL: ${{ secrets.OPENVIDU_CALL_NEXT_URL }}
OPENVIDU_SECRET: ${{ secrets.OPENVIDU_CALL_NEXT_SECRET }}
run: npm run webcomponent:e2e-pro-ci --prefix openvidu-components-angular

View File

@ -1,445 +0,0 @@
name: openvidu-components-angular Tests
on:
push:
branches:
- master
paths:
- 'openvidu-components-angular/**'
- '.github/workflows/openvidu-components-angular-tests.yml'
pull_request:
branches:
- master
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA'
required: false
default: ''
jobs:
test_setup:
name: Test setup
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Commit URL
run: echo https://github.com/OpenVidu/openvidu/commit/${{ inputs.commit_sha || github.sha }}
- name: Send Dispatch Event
env:
GITHUB_TOKEN: ${{ secrets.OPENVIDU_DISPATCH_EVENT_GA }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message || 'Manually' }}
COMMIT_URL: ${{ github.event.commits[0].url || 'Manually' }}
BRANCH_NAME: ${{ github.ref_name }}
run: |
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'", "branch-name":"'"$BRANCH_NAME"'"}}'
nested_events:
needs: test_setup
name: Nested events
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Install dependencies
run: |
cd openvidu-components-angular
npm install
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested components E2E event tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-events --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
nested_structural_directives:
needs: test_setup
name: Nested Structural Directives
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested structural directives tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-structural-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
nested_attribute_directives:
needs: test_setup
name: Nested Attribute Directives
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested attribute directives tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-attribute-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_directives:
needs: test_setup
name: API Directives Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_internal_directives:
needs: test_setup
name: Internal Directives Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-internal-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_chat:
needs: test_setup
name: Chat E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-chat --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_events:
needs: test_setup
name: Events E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-events --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_media_devices:
needs: test_setup
name: Media devices E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-media-devices --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_panels:
needs: test_setup
name: Panels E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-panels --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_screen_sharing:
needs: test_setup
name: Screen sharing E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-screensharing --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_stream:
needs: test_setup
name: Stream E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -v $(pwd)/openvidu-components-angular/e2e/assets:/e2e-assets selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-stream --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_toolbar:
needs: test_setup
name: Toolbar E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Webcomponent E2E
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-toolbar --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main

View File

@ -1,56 +0,0 @@
name: OpenVidu integration tests
on:
push:
branches:
- master
paths:
- "openvidu-test-integration/**"
- ".github/workflows/openvidu-integration-tests.yml"
workflow_dispatch:
jobs:
integration-tests:
name: Integration tests
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Configure OpenVidu Local Deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
with:
ref-openvidu-local-deployment: development
pre_startup_commands: |
sed -i 's/interval: 10s/interval: 1s/' livekit.yaml
sed -i '/interval: 1s/a \ fixer_interval: 10s' livekit.yaml
- name: Install LiveKit CLI
run: |
curl -sSL https://get.livekit.io/cli | bash
- name: Checkout current repository
uses: actions/checkout@v4
with:
path: openvidu
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
working-directory: ./openvidu/openvidu-test-integration
run: npm ci
- name: Run tests
working-directory: ./openvidu/openvidu-test-integration
run: npm run test:ci
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: openvidu-integration-tests-report
path: ./openvidu/openvidu-test-integration/test-results.json
retention-days: 7
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main

View File

@ -39,7 +39,7 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
OpenVidu has been supported under project "CPP2021-008720 NewGenVidu: An elastic, user-friendly and privacy-friendly videoconferencing platform", funded by MCIN/AEI/10.13039/501100011033 and by the European Union-NextGenerationEU/PRTR.
<img height="75px" src="https://docs.openvidu.io/en/stable/img/logos/support.jpg">
<img height="75px" src="https://openvidu.io/img/logos/support.jpg">
## Sponsors

View File

@ -10,4 +10,9 @@
node_modules
dist/
docs/
openvidu-webcomponent/
coverage/**
e2e/webcomponent-app/openvidu-webcomponent-*.css
e2e/webcomponent-app/openvidu-webcomponent-*.js
e2e/assets/*

View File

@ -18,8 +18,7 @@
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": {
"base": "dist/openvidu-components-testapp",
"browser": ""
"base": "dist/openvidu-components-testapp"
},
"index": "src/index.html",
"polyfills": ["zone.js"],
@ -149,6 +148,81 @@
}
}
}
},
"openvidu-webcomponent": {
"projectType": "application",
"root": "",
"sourceRoot": "src",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/openvidu-webcomponent-rc",
"index": "src/index.html",
"main": "src/app/openvidu-webcomponent/openvidu-webcomponent.main.ts",
"polyfills": ["zone.js"],
"tsConfig": "src/app/openvidu-webcomponent/tsconfig.openvidu-webcomponent.json",
"aot": true,
"assets": ["src/favicon.ico"],
"styles": ["src/app/openvidu-webcomponent/openvidu-webcomponent.component.scss"],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "1mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
},
"testing": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.testing.ts"
}
],
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "1mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
}
}
}
}
}
},
"cli": {

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
export const LAUNCH_MODE = process.env.LAUNCH_MODE || 'DEV';
export const OPENVIDU_CALL_SERVER = process.env.OPENVIDU_CALL_SERVER || 'http://localhost:5000';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;

View File

@ -1,84 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { OpenViduComponentsPO } from './utils.po.test';
import { TestAppConfig } from './selenium.conf';
let url = '';
describe('Testing Internal Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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);
url = `${TestAppConfig.appUrl}&roomName=INTERNAL_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
});
afterEach(async () => {
try {
} catch (error) {}
await browser.sleep(500);
await browser.quit();
});
it('should show/hide toolbar view recording button with toolbarViewRecordingsButton directive', async () => {
await browser.get(`${url}&prejoin=false&toolbarViewRecordingsButton=true`);
await utils.checkSessionIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
});
it('should show/hide participant name in prejoin with prejoinDisplayParticipantName directive', async () => {
await browser.get(`${url}&prejoin=true`);
await utils.checkPrejoinIsPresent();
expect(await utils.isPresent('.participant-name-container')).toBeTrue();
await browser.get(`${url}&prejoin=true&prejoinDisplayParticipantName=false`);
await browser.navigate().refresh();
await utils.checkPrejoinIsPresent();
expect(await utils.isPresent('.participant-name-container')).toBeFalse();
});
it('should show/hide view recordings button with recordingActivityViewRecordingsButton directive', async () => {
await browser.get(`${url}&prejoin=false&recordingActivityViewRecordingsButton=true`);
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
});
it('should show/hide start/stop recording buttons with recordingActivityStartStopRecordingButton directive', async () => {
await browser.get(`${url}&prejoin=false&recordingActivityStartStopRecordingButton=false`);
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#start-recording-btn')).toBeFalse();
await browser.sleep(3000);
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#start-recording-btn')).toBeTrue();
});
});

View File

@ -1,380 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('OpenVidu Components ATTRIBUTE toolbar directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the CHAT PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#chatPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if chat button does not exist
expect(await utils.isPresent('chat-panel-btn')).toBeFalse();
});
it('should HIDE the PARTICIPANTS PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#participantsPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if participants button does not exist
expect(await utils.isPresent('participants-panel-btn')).toBeFalse();
});
it('should HIDE the ACTIVITIES PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#activitiesPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if participants button does not exist
expect(await utils.isPresent('activities-panel-btn')).toBeFalse();
});
it('should HIDE the DISPLAY LOGO', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#displayLogo-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('branding-logo')).toBeFalse();
});
it('should HIDE the DISPLAY ROOM name', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#displayRoomName-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('session-name')).toBeFalse();
});
it('should HIDE the FULLSCREEN button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#fullscreenButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('#more-options-menu');
// Checking if fullscreen button is not present
expect(await utils.isPresent('#fullscreen-btn')).toBeFalse();
});
it('should HIDE the STREAMING button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#broadcastingButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('#more-options-menu');
// Checking if fullscreen button is not present
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
});
it('should HIDE the LEAVE button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#leaveButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('leave-btn')).toBeFalse();
});
it('should HIDE the SCREENSHARE button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#screenshareButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('screenshare-btn')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE stream directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the AUDIO detector', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#displayAudioDetection-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#session-container');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('audio-wave-container')).toBeFalse();
});
it('should HIDE the PARTICIPANT NAME', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#displayParticipantName-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#session-container');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('participant-name-container')).toBeFalse();
});
it('should HIDE the SETTINGS button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#settingsButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('settings-container')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE participant panels directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the participant MUTE button', async () => {
const fixedSession = `${url}?sessionId=fixedNameTesting`;
await browser.get(`${fixedSession}`);
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#muteButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
await utils.clickOn('#participants-panel-btn');
await utils.waitForElement('#participants-container');
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedSession}")`;
await browser.executeScript(newTabScript);
// Get tabs opened
const tabs = await browser.getAllWindowHandles();
// Focus on the last tab
browser.switchTo().window(tabs[1]);
await utils.clickOn('#apply-btn');
// Switch to first tab
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('#remote-participant-item');
expect(await utils.isPresent('mute-btn')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE activity panel directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the RECORDING activity', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#recordingActivity-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
});
it('should HIDE the STREAMING activity', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#broadcastingActivity-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
await utils.waitForElement('ov-recording-activity');
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
});
});

View File

@ -1,186 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('OpenVidu Components EVENTS', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should receive the onParticipantLeft event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Clicking to leave button
await utils.clickOn('#leave-btn');
// Checking if onLeaveButtonClicked has been received
await utils.waitForElement('#onParticipantLeft');
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
});
it('should receive the onVideoEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#camera-btn');
await utils.waitForElement('#onVideoEnabledChanged');
expect(await utils.isPresent('#onVideoEnabledChanged')).toBeTrue();
});
it('should receive the onAudioEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#mic-btn');
await utils.waitForElement('#onAudioEnabledChanged');
expect(await utils.isPresent('#onAudioEnabledChanged')).toBeTrue();
});
it('should receive the onScreenShareEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#screenshare-btn');
await utils.waitForElement('#onScreenShareEnabledChanged');
expect(await utils.isPresent('#onScreenShareEnabledChanged')).toBeTrue();
});
it('should receive the onFullscreenEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.toggleFullscreenFromToolbar();
await browser.sleep(1000);
await utils.waitForElement('#onFullscreenEnabledChanged');
expect(await utils.isPresent('#onFullscreenEnabledChanged')).toBeTrue();
});
it('should receive the onRecordingStartRequested event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.toggleRecordingFromToolbar();
await utils.waitForElement('#onRecordingStartRequested');
expect(await utils.isPresent('#onRecordingStartRequested')).toBeTrue();
});
it('should receive the onParticipantsPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('participants');
await utils.waitForElement('#onParticipantsPanelStatusChanged');
expect(await utils.isPresent('#onParticipantsPanelStatusChanged')).toBeTrue();
});
it('should receive the onChatPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('chat');
await utils.waitForElement('#onChatPanelStatusChanged');
expect(await utils.isPresent('#onChatPanelStatusChanged')).toBeTrue();
});
it('should receive the onActivitiesPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('activities');
await utils.waitForElement('#onActivitiesPanelStatusChanged');
expect(await utils.isPresent('#onActivitiesPanelStatusChanged')).toBeTrue();
});
it('should receive the onSettingsPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('#onSettingsPanelStatusChanged');
expect(await utils.isPresent('#onSettingsPanelStatusChanged')).toBeTrue();
});
});

View File

@ -1,806 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('E2E: Toolbar structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render only the custom toolbar (no additional buttons, no default toolbar)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has not been rendered
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeFalse();
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render the custom toolbar with additional custom buttons and hide the default toolbar', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#ovToolbarAdditionalButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has been rendered;
await utils.waitForElement('#custom-toolbar-additional-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeTrue();
const element = await browser.findElements(By.id('toolbar-additional-btn'));
expect(element.length).toEqual(2);
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render the custom toolbar with additional custom panel buttons and hide the default toolbar', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#ovToolbarAdditionalPanelButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has been rendered;
await utils.waitForElement('#custom-toolbar-additional-panel-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
const element = await browser.findElements(By.id('toolbar-additional-panel-btn'));
expect(element.length).toEqual(1);
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render only additional toolbar buttons (default toolbar visible, no custom toolbar)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovToolbarAdditionalButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if default toolbar is present
await utils.waitForElement('#default-toolbar');
expect(await utils.isPresent('#default-toolbar')).toBeTrue();
// Check if additional buttons are present
await utils.waitForElement('#custom-toolbar-additional-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeTrue();
element = await browser.findElements(By.id('toolbar-additional-btn'));
expect(element.length).toEqual(3);
// Check if custom toolbar not is present
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
});
it('should render only additional toolbar panel buttons (default toolbar visible, no custom toolbar)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovToolbarAdditionalPanelButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if default toolbar is present
await utils.waitForElement('#default-toolbar');
expect(await utils.isPresent('#default-toolbar')).toBeTrue();
// Check if additional buttons are present
await utils.waitForElement('#custom-toolbar-additional-panel-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
element = await browser.findElements(By.id('toolbar-additional-panel-btn'));
expect(element.length).toEqual(1);
// Check if custom toolbar not is present
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
});
});
describe('E2E: Panel structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render an additional custom panel with default panels', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Open additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
await browser.sleep(500);
// Check if custom panel is present
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
element = await utils.waitForElement('#additional-panel-title');
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
// Open the participants panel
await utils.clickOn('#participants-panel-btn');
await browser.sleep(500);
// Check if default panel is present
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Open additional panel again
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
// Close the additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render only the custom panel container (no children, no default panels)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with an additional panel only', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
// Check if custom panel is present
element = await browser.findElements(By.id('custom-additional-panel'));
expect(element.length).toEqual(1);
element = await utils.waitForElement('#additional-panel-title');
expect(await utils.isPresent('#additional-panel-title')).toBeTrue();
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render the custom panel container with a custom chat panel only', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
it('should render the custom panel container with a custom activities panel only', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening chat panel
await utils.clickOn('#activities-panel-btn');
// Check if default activities panel is not present
expect(await utils.isPresent('#default-activities-panel')).toBeFalse();
// Check if custom chat panel is not present
element = await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('#custom-activities-panel')).toBeTrue();
element = await utils.waitForElement('#activities-panel-title');
expect(await element.getAttribute('innerText')).toEqual('CUSTOM ACTIVITIES PANEL');
});
it('should render the custom panel container with a custom participants panel only (no children)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with a custom participants panel and a custom participant item', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Check if custom participant panel item is present
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Check if default participant panel item is not present
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
});
it('should render the custom panel container with a custom participants panel, custom participant item, and custom item element', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Check if custom participant panel item is present
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Check if custom participant panel item element is present
await utils.waitForElement('#custom-participants-panel-item-element');
expect(await utils.isPresent('#custom-participants-panel-item-element')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if default participant panel item is not present
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
});
it('should render only a custom activities panel (no default panel)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#activities-panel-btn');
// Check if default panel is not present
expect(await utils.isPresent('#default-activities-panel')).toBeFalse();
// Check if custom panel is present
await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('#custom-activities-panel')).toBeTrue();
// Check if activities panel is has content
await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).toBeTrue();
});
it('should render only a custom additional panel (no default panel)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-additional-panel');
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
element = await utils.waitForElement('#additional-panel-title');
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render only a custom chat panel (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
it('should render only a custom participants panel (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render only a custom participant panel item (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render only a custom participant panel item element (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
expect(await utils.isPresent('#custom-participants-panel-item')).toBeFalse();
expect(await utils.isPresent('#custom-participants-panel-item')).toBeFalse();
await utils.waitForElement('#custom-participants-panel-item-element');
expect(await utils.isPresent('#custom-participants-panel-item-element')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present;
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with both custom chat and participants panels', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
});
describe('E2E: Layout and stream structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render only the custom layout (no stream, no default layout)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom layout is present
await utils.waitForElement('#custom-layout');
expect(await utils.isPresent('#custom-layout')).toBeTrue();
// Check if default layout is not present
expect(await utils.isPresent('#default-layout')).toBeFalse();
// Check if custom stream is not present
expect(await utils.isPresent('#custom-stream')).toBeFalse();
// Check if video is not present
expect(await utils.isPresent('video')).toBeFalse();
});
it('should render the custom layout with a custom stream (no default layout/stream)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox');
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom layout is present
await utils.waitForElement('#custom-layout');
expect(await utils.isPresent('#custom-layout')).toBeTrue();
// Check if default layout is not present
expect(await utils.isPresent('default-layout')).toBeFalse();
// Check if custom stream is present
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('#custom-stream')).toBeTrue();
// Check if default stream is not present
expect(await utils.isPresent('default-stream')).toBeFalse();
// Check if video is present
await utils.waitForElement('video');
expect(await utils.isPresent('video')).toBeTrue();
});
it('should render only a custom stream (no custom layout, no default stream)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#apply-btn');
// Check if default layout is not present
await utils.waitForElement('#default-layout');
expect(await utils.isPresent('#default-layout')).toBeTrue();
// Check if custom stream is present
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('#custom-stream')).toBeTrue();
// Check if custom layout is not present
expect(await utils.isPresent('#custom-layout')).toBeFalse();
// Check if default stream is not present
expect(await utils.isPresent('default-stream')).toBeFalse();
// Check if video is present
await utils.waitForElement('video');
expect(await utils.isPresent('video')).toBeTrue();
});
});

View File

@ -10,14 +10,12 @@ interface BrowserConfig {
browserName: string;
}
const audioPath = LAUNCH_MODE === 'CI' ? `e2e-assets/audio_test.wav` : 'e2e/assets/audio_test.wav';
const chromeArguments = [
'--window-size=1300,1000',
// '--headless',
'--headless',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
`--use-file-for-fake-audio-capture=${audioPath}`
'--use-file-for-fake-audio-capture=e2e/assets/audio.wav'
];
const chromeArgumentsCI = [
'--window-size=1300,1000',
@ -31,10 +29,7 @@ const chromeArgumentsCI = [
'--disable-background-networking',
'--disable-default-apps',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
`--use-file-for-fake-audio-capture=${audioPath}`,
'--autoplay-policy=no-user-gesture-required',
'--allow-file-access-from-files'
'--use-fake-device-for-media-stream'
];
const chromeArgumentsWithoutMediaDevices = ['--headless', '--window-size=1300,900', '--deny-permission-prompts'];
const chromeArgumentsWithoutMediaDevicesCI = [
@ -51,17 +46,17 @@ const chromeArgumentsWithoutMediaDevicesCI = [
'--deny-permission-prompts'
];
export const TestAppConfig: BrowserConfig = {
appUrl: 'http://localhost:4200/#/call?staticVideos=false',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
export const WebComponentConfig: BrowserConfig = {
appUrl: 'http://localhost:8080/',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:3000/webdriver' : '',
browserName: 'chrome',
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),
browserOptions: new chrome.Options().addArguments(...(LAUNCH_MODE === 'CI' ? chromeArgumentsCI : chromeArguments))
};
export const NestedConfig: BrowserConfig = {
export const AngularConfig: BrowserConfig = {
appUrl: 'http://localhost:4200/#/testing',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:3000/webdriver' : '',
browserName: 'Chrome',
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),
browserOptions: new chrome.Options().addArguments(...(LAUNCH_MODE === 'CI' ? chromeArgumentsCI : chromeArguments))

View File

@ -6,7 +6,7 @@
"strict": true,
"outDir": "./dist",
"lib": ["es6"],
"types": [ "jasmine", "node" ],
"types": [ "mocha", "node" ],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},

View File

@ -1,4 +1,5 @@
import { By, until, WebDriver, WebElement } from 'selenium-webdriver';
import { expect } from 'chai';
import { By, Origin, until, WebDriver, WebElement } from 'selenium-webdriver';
export class OpenViduComponentsPO {
private TIMEOUT = 10 * 1000;
@ -30,44 +31,44 @@ export class OpenViduComponentsPO {
async checkPrejoinIsPresent(): Promise<void> {
await this.waitForElement('#prejoin-container');
expect(await this.isPresent('#prejoin-container')).toBe(true);
expect(await this.isPresent('#prejoin-container')).to.be.true;
}
async checkSessionIsPresent() {
await this.waitForElement('#call-container');
expect(await this.isPresent('#call-container')).toBe(true);
expect(await this.isPresent('#call-container')).to.be.true;
await this.waitForElement('#session-container');
expect(await this.isPresent('#session-container')).toBe(true);
expect(await this.isPresent('#session-container')).to.be.true;
}
async checkLayoutPresent(): Promise<void> {
await this.waitForElement('#layout-container');
expect(await this.isPresent('#layout-container')).toBe(true);
expect(await this.isPresent('#layout-container')).to.be.true;
await this.waitForElement('#layout');
expect(await this.isPresent('#layout')).toBe(true);
expect(await this.isPresent('#layout')).to.be.true;
}
async checkStreamIsPresent(): Promise<void> {
await this.waitForElement('.OV_stream');
expect(await this.isPresent('.OV_stream')).toBe(true);
expect(await this.isPresent('.OV_stream')).to.be.true;
}
async checkVideoElementIsPresent(): Promise<void> {
await this.waitForElement('video');
expect(await this.isPresent('video')).toBe(true);
expect(await this.isPresent('video')).to.be.true;
}
async checkToolbarIsPresent(): Promise<void> {
await this.waitForElement('#toolbar');
await this.waitForElement('#media-buttons-container');
expect(await this.isPresent('#media-buttons-container')).toBe(true);
expect(await this.isPresent('#media-buttons-container')).to.be.true;
}
async chceckProFeatureAlertIsPresent(): Promise<void> {
await this.waitForElement('ov-pro-feature-template');
expect(await this.isPresent('ov-pro-feature-template')).toBe(true);
expect(await this.isPresent('ov-pro-feature-template')).to.be.true;
}
async clickOn(selector: string): Promise<void> {
@ -103,27 +104,18 @@ export class OpenViduComponentsPO {
async toggleToolbarMoreOptions(): Promise<void> {
await this.waitForElement('#more-options-btn');
expect(await this.isPresent('#more-options-btn')).toBe(true);
expect(await this.isPresent('#more-options-btn')).to.be.true;
await this.clickOn('#more-options-btn');
await this.browser.sleep(500);
await this.waitForElement('#more-options-menu');
}
async disableScreenShare(): Promise<void> {
await this.waitForElement('#screenshare-btn');
await this.clickOn('#screenshare-btn');
await this.browser.sleep(500);
await this.waitForElement('#screenshare-menu');
await this.clickOn('#disable-screen-button');
await this.browser.sleep(1000);
}
async toggleRecordingFromToolbar() {
// Open more options menu
await this.toggleToolbarMoreOptions();
await this.waitForElement('#recording-btn');
expect(await this.isPresent('#recording-btn')).toBe(true);
expect(await this.isPresent('#recording-btn')).to.be.true;
await this.clickOn('#recording-btn');
}
@ -131,52 +123,16 @@ export class OpenViduComponentsPO {
// Open more options menu
await this.toggleToolbarMoreOptions();
await this.waitForElement('#fullscreen-btn');
expect(await this.isPresent('#fullscreen-btn')).toBe(true);
await this.clickOn('#fullscreen-btn');
}
async leaveRoom() {
try {
// Close any open panels or menus clicking on the body
await this.clickOn('body');
await this.browser.sleep(300);
// Verify that the leave button is present
await this.waitForElement('#leave-btn');
// Click on the leave button
await this.clickOn('#leave-btn');
// Verify that the session container is no longer present
await this.browser.wait(
async () => {
return !(await this.isPresent('#session-container'));
},
this.TIMEOUT,
'Session container should disappear after leaving room'
);
// Wait for the prejoin container to be present again
await this.browser.sleep(500);
// Verify that there are no video elements left in the DOM
const videoCount = await this.getNumberOfElements('video');
if (videoCount > 0) {
console.warn(`Warning: ${videoCount} video elements still present after leaving room`);
}
} catch (error) {
console.error('Error during leaveRoom:', error);
throw error;
}
const fullscreenButton = await this.waitForElement('#fullscreen-btn');
expect(await this.isPresent('#fullscreen-btn')).to.be.true;
await fullscreenButton.click();
}
async togglePanel(panelName: string) {
switch (panelName) {
case 'activities':
await this.waitForElement('#activities-panel-btn');
expect(await this.isPresent('#activities-panel-btn')).toBe(true);
expect(await this.isPresent('#activities-panel-btn')).to.be.true;
await this.clickOn('#activities-panel-btn');
break;
@ -195,7 +151,5 @@ export class OpenViduComponentsPO {
await this.clickOn('#toolbar-settings-btn');
break;
}
await this.browser.sleep(500);
}
}

View File

@ -0,0 +1,292 @@
import monkeyPatchMediaDevices from './utils/media-devices.js';
var MINIMAL;
var LANG;
var CAPTIONS_LANG;
var CUSTOM_LANG_OPTIONS;
var CUSTOM_CAPTIONS_LANG_OPTIONS;
var PREJOIN;
var VIDEO_ENABLED;
var AUDIO_ENABLED;
var SCREENSHARE_BUTTON;
var FULLSCREEN_BUTTON;
var ACTIVITIES_PANEL_BUTTON;
var RECORDING_BUTTON;
var BROADCASTING_BUTTON;
var CHAT_PANEL_BUTTON;
var DISPLAY_LOGO;
var DISPLAY_ROOM_NAME;
var DISPLAY_PARTICIPANT_NAME;
var DISPLAY_AUDIO_DETECTION;
var VIDEO_CONTROLS;
var LEAVE_BUTTON;
var PARTICIPANT_MUTE_BUTTON;
var PARTICIPANTS_PANEL_BUTTON;
var ACTIVITIES_RECORDING_ACTIVITY;
var ACTIVITIES_BROADCASTING_ACTIVITY;
var RECORDING_ERROR;
var BROADCASTING_ERROR;
var TOOLBAR_SETTINGS_BUTTON;
var CAPTIONS_BUTTON;
var ROOM_NAME;
var FAKE_DEVICES;
var FAKE_RECORDINGS;
var PARTICIPANT_NAME;
var OPENVIDU_CALL_SERVER_URL;
// var OPENVIDU_SECRET;
document.addEventListener('DOMContentLoaded', () => {
var url = new URL(window.location.href);
OPENVIDU_CALL_SERVER_URL = url.searchParams.get('OV_URL');
// OPENVIDU_SECRET = url.searchParams.get('OV_SECRET');
FAKE_DEVICES = url.searchParams.get('fakeDevices') === null ? false : url.searchParams.get('fakeDevices') === 'true';
FAKE_RECORDINGS = url.searchParams.get('fakeRecordings') === null ? false : url.searchParams.get('fakeRecordings') === 'true';
// Directives
MINIMAL = url.searchParams.get('minimal') === null ? false : url.searchParams.get('minimal') === 'true';
LANG = url.searchParams.get('lang') || 'en';
CUSTOM_LANG_OPTIONS = url.searchParams.get('langOptions') === null ? false : url.searchParams.get('langOptions') === 'true';
// CAPTIONS_LANG = url.searchParams.get('captionsLang') || 'en-US';
// CUSTOM_CAPTIONS_LANG_OPTIONS = url.searchParams.get('captionsLangOptions') === null ? false : url.searchParams.get('captionsLangOptions') === 'true';
PARTICIPANT_NAME =
url.searchParams.get('participantName') === null
? 'TEST_USER' + Math.random().toString(36).substr(2, 9)
: url.searchParams.get('participantName');
PREJOIN = url.searchParams.get('prejoin') === null ? true : url.searchParams.get('prejoin') === 'true';
VIDEO_ENABLED = url.searchParams.get('videoEnabled') === null ? true : url.searchParams.get('videoEnabled') === 'true';
AUDIO_ENABLED = url.searchParams.get('audioEnabled') === null ? true : url.searchParams.get('audioEnabled') === 'true';
SCREENSHARE_BUTTON = url.searchParams.get('screenshareBtn') === null ? true : url.searchParams.get('screenshareBtn') === 'true';
RECORDING_BUTTON =
url.searchParams.get('toolbarRecordingButton') === null ? true : url.searchParams.get('toolbarRecordingButton') === 'true';
FULLSCREEN_BUTTON = url.searchParams.get('fullscreenBtn') === null ? true : url.searchParams.get('fullscreenBtn') === 'true';
BROADCASTING_BUTTON =
url.searchParams.get('toolbarBroadcastingButton') === null ? true : url.searchParams.get('toolbarBroadcastingButton') === 'true';
if (url.searchParams.get('broadcastingError') !== null) {
BROADCASTING_ERROR = url.searchParams.get('broadcastingError');
}
TOOLBAR_SETTINGS_BUTTON =
url.searchParams.get('toolbarSettingsBtn') === null ? true : url.searchParams.get('toolbarSettingsBtn') === 'true';
CAPTIONS_BUTTON = url.searchParams.get('toolbarCaptionsBtn') === null ? true : url.searchParams.get('toolbarCaptionsBtn') === 'true';
LEAVE_BUTTON = url.searchParams.get('leaveBtn') === null ? true : url.searchParams.get('leaveBtn') === 'true';
ACTIVITIES_PANEL_BUTTON =
url.searchParams.get('activitiesPanelBtn') === null ? true : url.searchParams.get('activitiesPanelBtn') === 'true';
CHAT_PANEL_BUTTON = url.searchParams.get('chatPanelBtn') === null ? true : url.searchParams.get('chatPanelBtn') === 'true';
PARTICIPANTS_PANEL_BUTTON =
url.searchParams.get('participantsPanelBtn') === null ? true : url.searchParams.get('participantsPanelBtn') === 'true';
ACTIVITIES_BROADCASTING_ACTIVITY =
url.searchParams.get('activitiesPanelBroadcastingActivity') === null
? true
: url.searchParams.get('activitiesPanelBroadcastingActivity') === 'true';
ACTIVITIES_RECORDING_ACTIVITY =
url.searchParams.get('activitiesPanelRecordingActivity') === null
? true
: url.searchParams.get('activitiesPanelRecordingActivity') === 'true';
if (url.searchParams.get('recordingError') !== null) {
RECORDING_ERROR = url.searchParams.get('recordingError');
}
DISPLAY_LOGO = url.searchParams.get('displayLogo') === null ? true : url.searchParams.get('displayLogo') === 'true';
DISPLAY_ROOM_NAME = url.searchParams.get('displayRoomName') === null ? true : url.searchParams.get('displayRoomName') === 'true';
DISPLAY_PARTICIPANT_NAME =
url.searchParams.get('displayParticipantName') === null ? true : url.searchParams.get('displayParticipantName') === 'true';
DISPLAY_AUDIO_DETECTION =
url.searchParams.get('displayAudioDetection') === null ? true : url.searchParams.get('displayAudioDetection') === 'true';
VIDEO_CONTROLS = url.searchParams.get('videoControls') === null ? true : url.searchParams.get('videoControls') === 'true';
PARTICIPANT_MUTE_BUTTON =
url.searchParams.get('participantMuteBtn') === null ? true : url.searchParams.get('participantMuteBtn') === 'true';
ROOM_NAME = url.searchParams.get('roomName') === null ? `E2ESession${Math.floor(Date.now())}` : url.searchParams.get('roomName');
var webComponent = document.querySelector('openvidu-webcomponent');
webComponent.addEventListener('onTokenRequested', (event) => {
appendElement('onTokenRequested');
console.log('Token ready', event.detail);
joinSession(ROOM_NAME, event.detail);
});
webComponent.addEventListener('onReadyToJoin', (event) => appendElement('onReadyToJoin'));
webComponent.addEventListener('onRoomDisconnected', (event) => appendElement('onRoomDisconnected'));
webComponent.addEventListener('onVideoEnabledChanged', (event) => appendElement('onVideoEnabledChanged-' + event.detail));
webComponent.addEventListener('onVideoDeviceChanged', (event) => appendElement('onVideoDeviceChanged'));
webComponent.addEventListener('onAudioEnabledChanged', (eSESSIONvent) => appendElement('onAudioEnabledChanged-' + event.detail));
webComponent.addEventListener('onAudioDeviceChanged', (event) => appendElement('onAudioDeviceChanged'));
webComponent.addEventListener('onScreenShareEnabledChanged', (event) => appendElement('onScreenShareEnabledChanged'));
webComponent.addEventListener('onParticipantsPanelStatusChanged', (event) =>
appendElement('onParticipantsPanelStatusChanged-' + event.detail.isOpened)
);
webComponent.addEventListener('onLangChanged', (event) => appendElement('onLangChanged-' + event.detail.lang));
webComponent.addEventListener('onChatPanelStatusChanged', (event) =>
appendElement('onChatPanelStatusChanged-' + event.detail.isOpened)
);
webComponent.addEventListener('onActivitiesPanelStatusChanged', (event) =>
appendElement('onActivitiesPanelStatusChanged-' + event.detail.isOpened)
);
webComponent.addEventListener('onSettingsPanelStatusChanged', (event) =>
appendElement('onSettingsPanelStatusChanged-' + event.detail.isOpened)
);
webComponent.addEventListener('onFullscreenEnabledChanged', (event) => appendElement('onFullscreenEnabledChanged-' + event.detail));
webComponent.addEventListener('onRecordingStartRequested', async (event) => {
appendElement('onRecordingStartRequested-' + event.detail.roomName);
// Can't test the recording
// RECORDING_ID = await startRecording(SESSION_NAME);
});
// Can't test the recording
// webComponent.addEventListener('onRecordingStopRequested', async (event) => {
// appendElement('onRecordingStopRequested-' + event.detail.roomName);
// await stopRecording(RECORDING_ID);
// });
webComponent.addEventListener('onRecordingStopRequested', async (event) => {
appendElement('onRecordingStopRequested-' + event.detail.roomName);
});
// Can't test the recording
// webComponent.addEventListener('onActivitiesPanelStopRecordingClicked', async (event) => {
// appendElement('onActivitiesPanelStopRecordingClicked');
// await stopRecording(RECORDING_ID);
// });
webComponent.addEventListener('onRecordingDeleteRequested', (event) => {
const { roomName, recordingId } = event.detail;
appendElement(`onRecordingDeleteRequested-${roomName}-${recordingId}`);
});
webComponent.addEventListener('onBroadcastingStartRequested', async (event) => {
const { roomName, broadcastUrl } = event.detail;
appendElement(`onBroadcastingStartRequested-${roomName}-${broadcastUrl}`);
});
webComponent.addEventListener('onActivitiesPanelStopBroadcastingClicked', async (event) => {
appendElement('onActivitiesPanelStopBroadcastingClicked');
});
webComponent.addEventListener('onRoomCreated', (event) => {
var room = event.detail;
appendElement('onRoomCreated');
room.on('disconnected', (e) => {
appendElement('roomDisconnected');
});
});
webComponent.addEventListener('onParticipantCreated', (event) => {
var participant = event.detail;
appendElement(`${participant.name}-onParticipantCreated`);
});
setWebcomponentAttributes();
});
function setWebcomponentAttributes() {
var webComponent = document.querySelector('openvidu-webcomponent');
webComponent.participantName = PARTICIPANT_NAME;
webComponent.minimal = MINIMAL;
webComponent.lang = LANG;
if (CUSTOM_LANG_OPTIONS) {
webComponent.langOptions = [
{ name: 'Esp', lang: 'es' },
{ name: 'Eng', lang: 'en' }
];
}
// TODO: Uncomment when the captions are implemented
// webComponent.captionsLang = CAPTIONS_LANG;
// if (CUSTOM_CAPTIONS_LANG_OPTIONS) {
// webComponent.captionsLangOptions = [
// { name: 'Esp', lang: 'es-ES' },
// { name: 'Eng', lang: 'en-US' }
// ];
// }
if (FAKE_DEVICES) {
console.warn('Using fake devices');
monkeyPatchMediaDevices();
}
if (FAKE_RECORDINGS) {
console.warn('Using fake recordings');
webComponent.recordingActivityRecordingsList = [{ status: 'ready', filename: 'fakeRecording' }];
}
if (BROADCASTING_ERROR) {
webComponent.broadcastingActivityBroadcastingError = { message: BROADCASTING_ERROR, broadcastAvailable: true };
}
webComponent.prejoin = PREJOIN;
webComponent.videoEnabled = VIDEO_ENABLED;
webComponent.audioEnabled = AUDIO_ENABLED;
webComponent.toolbarScreenshareButton = SCREENSHARE_BUTTON;
webComponent.toolbarFullscreenButton = FULLSCREEN_BUTTON;
webComponent.toolbarSettingsButton = TOOLBAR_SETTINGS_BUTTON;
// webComponent.toolbarCaptionsButton = CAPTIONS_BUTTON;
webComponent.toolbarLeaveButton = LEAVE_BUTTON;
webComponent.toolbarRecordingButton = RECORDING_BUTTON;
webComponent.toolbarBroadcastingButton = BROADCASTING_BUTTON;
webComponent.toolbarActivitiesPanelButton = ACTIVITIES_PANEL_BUTTON;
webComponent.toolbarChatPanelButton = CHAT_PANEL_BUTTON;
webComponent.toolbarParticipantsPanelButton = PARTICIPANTS_PANEL_BUTTON;
webComponent.toolbarDisplayLogo = DISPLAY_LOGO;
webComponent.toolbarDisplayRoomName = DISPLAY_ROOM_NAME;
webComponent.streamDisplayParticipantName = DISPLAY_PARTICIPANT_NAME;
webComponent.streamDisplayAudioDetection = DISPLAY_AUDIO_DETECTION;
webComponent.streamVideoControls = VIDEO_CONTROLS;
webComponent.participantPanelItemMuteButton = PARTICIPANT_MUTE_BUTTON;
webComponent.activitiesPanelRecordingActivity = ACTIVITIES_RECORDING_ACTIVITY;
webComponent.activitiesPanelBroadcastingActivity = ACTIVITIES_BROADCASTING_ACTIVITY;
webComponent.recordingActivityRecordingError = RECORDING_ERROR;
}
function appendElement(id) {
var eventsDiv = document.getElementById('events');
eventsDiv.setAttribute('style', 'position: absolute;');
var element = document.createElement('div');
element.setAttribute('id', id);
element.setAttribute('style', 'height: 1px;');
eventsDiv.appendChild(element);
}
async function joinSession(roomName, participantName) {
var webComponent = document.querySelector('openvidu-webcomponent');
console.log('Joining session', roomName, participantName);
try {
webComponent.token = await getToken(roomName, participantName);
} catch (error) {
webComponent.tokenError = error;
}
}
async function getToken(roomName, participantName) {
try {
const response = await fetch(OPENVIDU_CALL_SERVER_URL + '/call/api/rooms', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
// 'Authorization': 'Basic ' + btoa('OPENVIDUAPP:' + OPENVIDU_SECRET),
},
body: JSON.stringify({
participantName,
roomName
})
});
if (!response.ok) {
throw new Error('Failed to fetch token');
}
const data = await response.json();
return data.token;
} catch (error) {
console.error(error);
throw error;
}
}

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>openvidu-web-component</title>
<script type="module" src="utils/filter-stream.js"></script>
<!-- <script type="module" src="utils/shader-renderer.js"></script> -->
<script type="module" src="utils/media-devices.js"></script>
<script type="module" src="app.js"></script>
<script src="openvidu-webcomponent-dev.js"></script>
<link rel="stylesheet" href="openvidu-webcomponent-dev.css" />
<style>
:root {
--ov-primary-color: #303030;
--ov-secondary-color: #3e3f3f;
--ov-tertiary-color: #598eff;
--ov-warn-color: #eb5144;
--ov-accent-color: #ffae35;
--ov-light-color: #e6e6e6;
--ov-logo-background-color: #3a3d3d;
--ov-text-color: #ffffff;
--ov-panel-text-color: #1d1d1d;
--ov-panel-background: #ffffff;
--ov-buttons-radius: 50%;
--ov-leave-button-radius: 10px;
--ov-video-radius: 5px;
--ov-panel-radius: 5px;
}
</style>
</head>
<body>
<div id="events"></div>
<!-- OpenVidu Web Component -->
<openvidu-webcomponent></openvidu-webcomponent>
</body>
</html>

View File

@ -1,21 +1,21 @@
// Ideally we'd use an editor or import shaders directly from the API.
import { FilterStream } from './filter-stream.js';
export const monkeyPatchMediaDevices = () => {
export default function monkeyPatchMediaDevices() {
const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices;
const getUserMediaFn = MediaDevices.prototype.getUserMedia;
const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia;
const fakeVideoDevice = {
deviceId: 'virtual_video',
groupId: '',
deviceId: 'virtual',
groupID: '',
kind: 'videoinput',
label: 'custom_fake_video_1'
};
const fakeAudioDevice = {
deviceId: 'virtual_audio',
groupId: '',
deviceId: 'virtual',
groupID: '',
kind: 'audioinput',
label: 'custom_fake_audio_1'
};
@ -29,21 +29,8 @@ export const monkeyPatchMediaDevices = () => {
const getUserMediaMonkeyPatch = async function () {
const args = arguments[0];
if (args.audio && (args.audio.deviceId === 'virtual_audio' || args.audio.deviceId?.exact === 'virtual_audio')) {
const constraints = {
audio: {
facingMode: args.facingMode,
advanced: args.audio.advanced,
deviceId: fakeAudioDevice.deviceId
},
video: false
};
const res = await getUserMediaFn.call(navigator.mediaDevices, constraints);
return res;
} else if (args.video && (args.video.deviceId === 'virtual_video' || args.video.deviceId?.exact === 'virtual_video')) {
const { deviceId, advanced, width, height } = args.video;
if (deviceId === 'virtual' || deviceId?.exact === 'virtual') {
const constraints = {
video: {
facingMode: args.facingMode,

View File

@ -1,35 +1,30 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
let url = '';
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Testing API Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
url = `${TestAppConfig.appUrl}&roomName=API_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
if (await utils.isPresent('#session-container')) {
await utils.leaveRoom();
}
} catch (error) {}
await browser.sleep(500);
await browser.quit();
});
@ -39,7 +34,7 @@ describe('Testing API Directives', () => {
await utils.checkPrejoinIsPresent();
// Checking if audio detection is not displayed
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
expect(await utils.isPresent('#audio-wave-container')).to.be.false;
const joinButton = await utils.waitForElement('#join-button');
await joinButton.click();
@ -57,31 +52,31 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeFalse();
expect(await utils.isPresent('#screenshare-btn')).to.be.false;
// Checking if more options button is not present
expect(await utils.isPresent('#more-options-btn')).toBeFalse();
expect(await utils.isPresent('#more-options-btn')).to.be.false;
// Checking if participants panel button is not present
expect(await utils.isPresent('#participants-panel-btn')).toBeFalse();
expect(await utils.isPresent('#participants-panel-btn')).to.be.false;
// Checking if activities panel button is not present
expect(await utils.isPresent('#activities-panel-btn')).toBeFalse();
expect(await utils.isPresent('#activities-panel-btn')).to.be.false;
// Checking if logo is not displayed
expect(await utils.isPresent('#branding-logo')).toBeFalse();
expect(await utils.isPresent('#branding-logo')).to.be.false;
// Checking if session name is not displayed
expect(await utils.isPresent('#session-name')).toBeFalse();
expect(await utils.isPresent('#session-name')).to.be.false;
// Checking if nickname is not displayed
expect(await utils.getNumberOfElements('#participant-name-container')).toEqual(0);
expect(await utils.getNumberOfElements('#participant-name-container')).equals(0);
// Checking if audio detection is not displayed
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
expect(await utils.isPresent('#audio-wave-container')).to.be.false;
// Checking if settings button is not displayed
expect(await utils.isPresent('#settings-container')).toBeFalse();
expect(await utils.isPresent('#settings-container')).to.be.false;
});
it('should change the UI LANG in prejoin page', async () => {
@ -92,7 +87,7 @@ describe('Testing API Directives', () => {
await utils.waitForElement('#lang-btn-compact');
const element = await utils.waitForElement('#join-button');
expect(await element.getText()).toEqual('Unirme ahora');
expect(await element.getText()).equal('Unirme ahora');
});
it('should change the UI LANG in room page', async () => {
@ -104,12 +99,12 @@ describe('Testing API Directives', () => {
await utils.togglePanel('settings');
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('#default-settings-panel')).toBeTrue();
expect(await utils.isPresent('#default-settings-panel')).to.be.true;
const panelTitle = await utils.waitForElement('.panel-title');
expect(await panelTitle.getText()).toEqual('Configuración');
expect(await panelTitle.getText()).equal('Configuración');
const element = await utils.waitForElement('#lang-selected-name');
expect(await element.getAttribute('innerText')).toEqual('Español');
expect(await element.getAttribute('innerText')).equal('Español');
});
it('should override the LANG OPTIONS', async () => {
@ -119,7 +114,7 @@ describe('Testing API Directives', () => {
await utils.waitForElement('#lang-btn-compact');
await utils.clickOn('#lang-btn-compact');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.lang-menu-opt')).toEqual(2);
expect(await utils.getNumberOfElements('.lang-menu-opt')).equals(2);
await utils.clickOn('.lang-menu-opt');
await browser.sleep(500);
@ -141,7 +136,7 @@ describe('Testing API Directives', () => {
await browser.sleep(500);
expect(await utils.getNumberOfElements('.lang-menu-opt')).toEqual(2);
expect(await utils.getNumberOfElements('.lang-menu-opt')).equals(2);
});
it('should show the PREJOIN page', async () => {
@ -170,11 +165,11 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
expect(await utils.isPresent('#screenshare-btn')).to.be.true;
});
it('should show the token error WITH prejoin page', async () => {
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TEST_TOKEN&participantName=PNAME`;
const fixedUrl = `${url}&roomName=TEST_TOKEN&participantName=PNAME`;
await browser.get(`${fixedUrl}`);
// Checking if prejoin page exist
@ -200,11 +195,11 @@ describe('Testing API Directives', () => {
// Checking if token error is displayed
await utils.waitForElement('#token-error');
expect(await utils.isPresent('#token-error')).toBeTrue();
expect(await utils.isPresent('#token-error')).to.be.true;
});
it('should show the token error WITHOUT prejoin page', async () => {
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TOKEN_ERROR&prejoin=false&participantName=PNAME`;
const fixedUrl = `${url}&roomName=TOKEN_ERROR&prejoin=false&participantName=PNAME`;
await browser.get(`${fixedUrl}`);
// Checking if session container is present
@ -220,7 +215,7 @@ describe('Testing API Directives', () => {
// Checking if token error is displayed
await utils.waitForElement('#openvidu-dialog');
expect(await utils.isPresent('#openvidu-dialog')).toBeTrue();
expect(await utils.isPresent('#openvidu-dialog')).to.be.true;
});
it('should run the app with VIDEO DISABLED in prejoin page', async () => {
@ -229,20 +224,21 @@ describe('Testing API Directives', () => {
await utils.checkPrejoinIsPresent();
// Checking if video is displayed
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('video')).equals(1);
// Checking if virtual background button is disabled
// const button = await utils.waitForElement('#background-effects-btn');
// expect(await button.isEnabled()).to.be.false;
await utils.waitForElement('#videocam_off');
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).toBeTrue();
expect(await utils.getNumberOfElements('video')).equals(1);
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).to.be.true;
});
it('should run the app with VIDEO DISABLED and WITHOUT PREJOIN page', async () => {
@ -253,74 +249,58 @@ describe('Testing API Directives', () => {
await utils.checkLayoutPresent();
// Checking if video is displayed
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('#video-poster')).toEqual(1);
expect(await utils.getNumberOfElements('video')).equals(1);
expect(await utils.getNumberOfElements('#video-poster')).equals(1);
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).toBeTrue();
expect(await utils.isPresent('#videocam_off')).to.be.true;
});
it('should run the app with AUDIO DISABLED in prejoin page', async () => {
// let isAudioEnabled;
// const script = 'return document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0].enabled;';
await browser.get(`${url}&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
// Checking if video is displayed
await utils.checkVideoElementIsPresent();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
// Checking if audio track is disabled/muted
// isAudioEnabled = await browser.executeScript(script);
// expect(isAudioEnabled).to.be.false;
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
expect(await utils.isPresent('#mic_off')).to.be.true;
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
// isAudioEnabled = await browser.executeScript(script);
// expect(isAudioEnabled).to.be.false;
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
expect(await utils.isPresent('#mic_off')).to.be.true;
});
it('should run the app with AUDIO DISABLED and WITHOUT PREJOIN page', async () => {
// let isAudioEnabled;
// const audioEnableScript = 'return document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0].enabled;';
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
await browser.sleep(1000);
await utils.checkSessionIsPresent();
// Checking if video is displayed
await utils.checkVideoElementIsPresent();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
// Checking if audio track is disabled/muted
// isAudioEnabled = await browser.executeScript(audioEnableScript);
// expect(isAudioEnabled).to.be.false;
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
});
it('should run the app without camera button', async () => {
await browser.get(`${url}&prejoin=false&cameraBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if camera button is not present
expect(await utils.isPresent('#camera-btn')).toBeFalse();
});
it('should run the app without microphone button', async () => {
await browser.get(`${url}&prejoin=false&microphoneBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if microphone button is not present
expect(await utils.isPresent('#microphone-btn')).toBeFalse();
expect(await utils.isPresent('#mic_off')).to.be.true;
});
it('should HIDE the SCREENSHARE button', async () => {
@ -332,7 +312,7 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeFalse();
expect(await utils.isPresent('#screenshare-btn')).to.be.false;
});
it('should HIDE the FULLSCREEN button', async () => {
@ -344,10 +324,10 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.getNumberOfElements('#fullscreen-btn')).toEqual(0);
expect(await utils.getNumberOfElements('#fullscreen-btn')).equals(0);
});
xit('should HIDE the CAPTIONS button', async () => {
it('should HIDE the CAPTIONS button', async () => {
await browser.get(`${url}&prejoin=false&toolbarCaptionsBtn=false`);
await utils.checkSessionIsPresent();
@ -358,16 +338,16 @@ describe('Testing API Directives', () => {
await utils.toggleToolbarMoreOptions();
// Checking if captions button is not present
expect(await utils.isPresent('#captions-btn')).toBeFalse();
expect(await utils.isPresent('#captions-btn')).to.be.false;
await utils.clickOn('#toolbar-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
expect(await utils.isPresent('.settings-container')).to.be.true;
expect(await utils.isPresent('#captions-opt')).toBeFalse();
expect(await utils.isPresent('#captions-opt')).to.be.false;
});
it('should HIDE the TOOLBAR RECORDING button', async () => {
@ -381,7 +361,7 @@ describe('Testing API Directives', () => {
await utils.toggleToolbarMoreOptions();
// Checking if recording button is not present
expect(await utils.isPresent('#recording-btn')).toBeFalse();
expect(await utils.isPresent('#recording-btn')).to.be.false;
});
it('should HIDE the TOOLBAR BROADCASTING button', async () => {
@ -395,7 +375,7 @@ describe('Testing API Directives', () => {
await utils.toggleToolbarMoreOptions();
// Checking if broadcasting button is not present
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
expect(await utils.isPresent('#broadcasting-btn')).to.be.false;
});
it('should HIDE the TOOLBAR SETTINGS button', async () => {
@ -409,7 +389,7 @@ describe('Testing API Directives', () => {
// Open more options menu
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#toolbar-settings-btn')).toBeFalse();
expect(await utils.isPresent('#toolbar-settings-btn')).to.be.false;
});
it('should HIDE the LEAVE button', async () => {
@ -421,7 +401,7 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if leave button is not present
expect(await utils.getNumberOfElements('#leave-btn')).toEqual(0);
expect(await utils.getNumberOfElements('#leave-btn')).equals(0);
});
it('should HIDE the ACTIVITIES PANEL button', async () => {
@ -433,7 +413,7 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if activities panel button is not present
expect(await utils.isPresent('#activities-panel-btn')).toBeFalse();
expect(await utils.isPresent('#activities-panel-btn')).to.be.false;
});
it('should HIDE the CHAT PANEL button', async () => {
@ -445,7 +425,7 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if chat panel button is not present
expect(await utils.isPresent('#chat-panel-btn')).toBeFalse();
expect(await utils.isPresent('#chat-panel-btn')).to.be.false;
});
it('should HIDE the PARTICIPANTS PANEL button', async () => {
@ -457,7 +437,7 @@ describe('Testing API Directives', () => {
await utils.checkToolbarIsPresent();
// Checking if participants panel button is not present
expect(await utils.isPresent('#participants-panel-btn')).toBeFalse();
expect(await utils.isPresent('#participants-panel-btn')).to.be.false;
});
it('should HIDE the LOGO', async () => {
@ -470,13 +450,13 @@ describe('Testing API Directives', () => {
// Checking if toolbar is present
await utils.waitForElement('#info-container');
expect(await utils.isPresent('#info-container')).toBeTrue();
expect(await utils.isPresent('#info-container')).to.be.true;
// Checking if logo is not displayed
expect(await utils.isPresent('#branding-logo')).toBeFalse();
expect(await utils.isPresent('#branding-logo')).to.be.false;
});
it('should HIDE the ROOM NAME', async () => {
it('should HIDE the SESSION NAME', async () => {
await browser.get(`${url}&prejoin=false&displayRoomName=false`);
await utils.checkSessionIsPresent();
@ -486,10 +466,10 @@ describe('Testing API Directives', () => {
// Checking if toolbar is present
await utils.waitForElement('#info-container');
expect(await utils.isPresent('#info-container')).toBeTrue();
expect(await utils.isPresent('#info-container')).to.be.true;
// Checking if session name is not displayed
expect(await utils.isPresent('#session-name')).toBeFalse();
expect(await utils.isPresent('#session-name')).to.be.false;
});
it('should HIDE the PARTICIPANT NAME', async () => {
@ -504,7 +484,7 @@ describe('Testing API Directives', () => {
await utils.checkStreamIsPresent();
// Checking if nickname is not present
expect(await utils.isPresent('#participant-name-container')).toBeFalse();
expect(await utils.isPresent('#participant-name-container')).to.be.false;
});
it('should HIDE the AUDIO DETECTION element', async () => {
@ -519,7 +499,7 @@ describe('Testing API Directives', () => {
await utils.checkStreamIsPresent();
// Checking if audio detection is not present
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
expect(await utils.isPresent('#audio-wave-container')).to.be.false;
});
it('should HIDE the STREAM VIDEO CONTROLS button', async () => {
@ -534,12 +514,12 @@ describe('Testing API Directives', () => {
await utils.checkStreamIsPresent();
// Checking if settings button is not present
expect(await utils.isPresent('.stream-video-controls')).toBeFalse();
expect(await utils.isPresent('.stream-video-controls')).to.be.false;
});
it('should HIDE the MUTE button in participants panel', async () => {
const roomName = 'e2etest';
const fixedUrl = `${TestAppConfig.appUrl}&prejoin=false&participantMuteBtn=false&roomName=${roomName}`;
const fixedUrl = `${url}&prejoin=false&participantMuteBtn=false&roomName=${roomName}`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
@ -552,15 +532,14 @@ describe('Testing API Directives', () => {
// Checking if participatns panel is displayed
await utils.waitForElement('#participants-container');
expect(await utils.isPresent('#participants-container')).toBeTrue();
expect(await utils.isPresent('#participants-container')).to.be.true;
// Checking remote participants item
expect(await utils.isPresent('#remote-participant-item')).toBeFalse();
expect(await utils.isPresent('#remote-participant-item')).to.be.false;
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedUrl}&participantName=SecondParticipant")`;
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
await browser.sleep(10000);
// Go to first tab
const tabs = await browser.getAllWindowHandles();
@ -568,9 +547,9 @@ describe('Testing API Directives', () => {
// Checking if mute button is not displayed in participant item
await utils.waitForElement('#remote-participant-item');
expect(await utils.isPresent('#remote-participant-item')).toBeTrue();
expect(await utils.isPresent('#remote-participant-item')).to.be.true;
expect(await utils.isPresent('#mute-btn')).toBeFalse();
expect(await utils.isPresent('#mute-btn')).to.be.false;
});
it('should HIDE the RECORDING ACTIVITY in activities panel', async () => {
@ -588,13 +567,86 @@ describe('Testing API Directives', () => {
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).toBeTrue();
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// await browser.sleep(1000);
// Checking if recording activity exists
await utils.waitForElement('.activities-body-container');
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
expect(await utils.isPresent('ov-recording-activity')).to.be.false;
});
it('should SHOW a RECORDING ERROR in activities panel', async () => {
let element;
const fixedUrl = `${url}&prejoin=false&recordingError=TEST_ERROR`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
element = await utils.waitForElement('#activities-panel-btn');
await element.click();
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// Checking if recording activity exists
await utils.waitForElement('#activities-container');
await utils.waitForElement('.activities-body-container');
await utils.waitForElement('ov-recording-activity');
expect(await utils.isPresent('ov-recording-activity')).to.be.true;
await utils.waitForElement('.failed');
expect(await utils.isPresent('.failed')).to.be.true;
// Open recording
await browser.sleep(500);
await utils.waitForElement('ov-recording-activity');
await utils.clickOn('ov-recording-activity');
await browser.sleep(500);
element = await utils.waitForElement('.recording-error');
expect(await element.getAttribute('innerText')).equal('"TEST_ERROR"');
expect(await utils.isPresent('.recording-error')).to.be.true;
});
it('should SHOW a BROADCASTING ERROR in activities panel', async () => {
let element;
const fixedUrl = `${url}&prejoin=false&broadcastingError=TEST_ERROR`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
element = await utils.waitForElement('#activities-panel-btn');
await element.click();
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// Checking if broadcasting activity exists
await utils.waitForElement('#activities-container');
await utils.waitForElement('.activities-body-container');
await utils.waitForElement('ov-broadcasting-activity');
expect(await utils.isPresent('ov-broadcasting-activity')).to.be.true;
const status = await utils.waitForElement('#broadcasting-status');
expect(await status.getAttribute('innerText')).equals('FAILED');
// Open broadcasting
await browser.sleep(500);
await utils.clickOn('ov-broadcasting-activity');
await browser.sleep(500);
element = await utils.waitForElement('#broadcasting-error');
expect(await element.getAttribute('innerText')).equal('TEST_ERROR');
});
it('should HIDE the BROADCASTING ACTIVITY in activities panel', async () => {
@ -610,12 +662,12 @@ describe('Testing API Directives', () => {
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).toBeTrue();
expect(await utils.isPresent('#default-activities-panel')).to.be.true;
// await browser.sleep(1000);
// Checking if recording activity exists
await utils.waitForElement('.activities-body-container');
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
expect(await utils.isPresent('ov-broadcasting-activity')).to.be.false;
});
});

View File

@ -1,8 +1,10 @@
import { expect } from 'chai';
import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
//TODO: Uncomment when captions are implemented
// describe('Testing captions features', () => {
@ -10,10 +12,10 @@ const url = TestAppConfig.appUrl;
// let utils: OpenViduComponentsPO;
// async function createChromeBrowser(): Promise<WebDriver> {
// return await new Builder()
// .forBrowser(TestAppConfig.browserName)
// .withCapabilities(TestAppConfig.browserCapabilities)
// .setChromeOptions(TestAppConfig.browserOptions)
// .usingServer(TestAppConfig.seleniumAddress)
// .forBrowser(WebComponentConfig.browserName)
// .withCapabilities(WebComponentConfig.browserCapabilities)
// .setChromeOptions(WebComponentConfig.browserOptions)
// .usingServer(WebComponentConfig.seleniumAddress)
// .build();
// }
@ -41,11 +43,11 @@ const url = TestAppConfig.appUrl;
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
@ -66,11 +68,11 @@ const url = TestAppConfig.appUrl;
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
@ -80,12 +82,12 @@ const url = TestAppConfig.appUrl;
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// expect(await utils.isPresent('.settings-container')).to.be.true;
// await utils.waitForElement('ov-captions-settings');
// // Expect caption button is not present
// expect(await utils.isPresent('#caption-settings-btn')).toBeFalse();
// expect(await utils.isPresent('#caption-settings-btn')).to.be.false;
// });
// it('should TOGGLE the CAPTIONS container from settings panel', async () => {
@ -103,11 +105,11 @@ const url = TestAppConfig.appUrl;
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
@ -117,18 +119,18 @@ const url = TestAppConfig.appUrl;
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// expect(await utils.isPresent('.settings-container')).to.be.true;
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// expect(await utils.isPresent('.captions-container')).to.be.true;
// await utils.clickOn('#captions-toggle-slide');
// expect(await utils.isPresent('.captions-container')).toBeFalse();
// expect(await utils.isPresent('.captions-container')).to.be.false;
// await browser.sleep(200);
// await utils.clickOn('#captions-toggle-slide');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// expect(await utils.isPresent('.captions-container')).to.be.true;
// });
// it('should change the CAPTIONS language', async () => {
@ -146,11 +148,11 @@ const url = TestAppConfig.appUrl;
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
@ -160,11 +162,11 @@ const url = TestAppConfig.appUrl;
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// expect(await utils.isPresent('.settings-container')).to.be.true;
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// expect(await utils.isPresent('.captions-container')).to.be.true;
// await utils.clickOn('.lang-button');
// await browser.sleep(500);
@ -173,7 +175,7 @@ const url = TestAppConfig.appUrl;
// await utils.clickOn('.panel-close-button');
// const button = await utils.waitForElement('#caption-settings-btn');
// expect(await button.getText()).toEqual('settingsEspañol');
// expect(await button.getText()).equals('settingsEspañol');
// });
// });

View File

@ -1,8 +1,10 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Testing CHAT features', () => {
let browser: WebDriver;
@ -10,10 +12,10 @@ describe('Testing CHAT features', () => {
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -23,10 +25,6 @@ describe('Testing CHAT features', () => {
});
afterEach(async () => {
try {
// leaving room if connected
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
@ -40,7 +38,7 @@ describe('Testing CHAT features', () => {
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.input-container')).to.be.true;
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('Test message');
@ -49,13 +47,13 @@ describe('Testing CHAT features', () => {
await utils.waitForElement('.message');
await utils.getNumberOfElements('.message');
expect(await utils.isPresent('.message')).toBeTrue();
expect(await utils.isPresent('.message')).to.be.true;
expect(await utils.getNumberOfElements('.message')).toEqual(1);
expect(await utils.getNumberOfElements('.message')).equals(1);
await input.sendKeys('Test message');
await utils.clickOn('#send-btn');
expect(await utils.getNumberOfElements('.message')).toEqual(2);
expect(await utils.getNumberOfElements('.message')).equals(2);
});
it('should receive a message', async () => {
@ -63,7 +61,6 @@ describe('Testing CHAT features', () => {
let pName = `participant${Math.floor(Math.random() * 1000)}`;
const fixedUrl = `${url}&prejoin=false&roomName=${roomName}`;
await browser.get(fixedUrl);
await browser.sleep(1000);
await utils.checkLayoutPresent();
// Starting new browser for adding a new participant
@ -79,7 +76,7 @@ describe('Testing CHAT features', () => {
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.input-container')).to.be.true;
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('test message');
@ -93,8 +90,8 @@ describe('Testing CHAT features', () => {
await browser.sleep(1000);
await utils.waitForElement('.message');
const participantName = await utils.waitForElement('.participant-name-container>p');
expect(await utils.getNumberOfElements('.message')).toEqual(1);
expect(await participantName.getText()).toEqual(pName);
expect(await utils.getNumberOfElements('.message')).equals(1);
expect(await participantName.getText()).equals(pName);
});
it('should send an url message and converts in a link', async () => {
@ -107,14 +104,14 @@ describe('Testing CHAT features', () => {
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.input-container')).to.be.true;
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('demos.openvidu.io');
await utils.clickOn('#send-btn');
await utils.waitForElement('.chat-message a');
expect(await utils.isPresent('.chat-message a')).toBeTrue();
await utils.waitForElement('.msg-content a');
expect(await utils.isPresent('.msg-content a')).to.be.true;
});
});

View File

@ -1,19 +1,21 @@
import { expect } from 'chai';
import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Testing videoconference EVENTS', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
const isHeadless: boolean = (TestAppConfig.browserOptions as any).options_.args.includes('--headless');
const isHeadless: boolean = (WebComponentConfig.browserOptions as any).options_.args.includes('--headless');
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -23,10 +25,6 @@ describe('Testing videoconference EVENTS', () => {
});
afterEach(async () => {
try {
// leaving room if connected
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
@ -34,7 +32,7 @@ describe('Testing videoconference EVENTS', () => {
await browser.get(`${url}`);
await utils.waitForElement('#prejoin-container');
expect(await utils.isPresent('#prejoin-container')).toBeTrue();
expect(await utils.isPresent('#prejoin-container')).to.be.true;
// Clicking to join button
await utils.waitForElement('#join-button');
@ -42,14 +40,14 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onReadyToJoin has been received
await utils.waitForElement('#onReadyToJoin');
expect(await utils.isPresent('#onReadyToJoin')).toBeTrue();
expect(await utils.isPresent('#onReadyToJoin')).to.be.true;
});
it('should receive the onTokenRequested event', async () => {
await browser.get(`${url}`);
await utils.waitForElement('#prejoin-container');
expect(await utils.isPresent('#prejoin-container')).toBeTrue();
expect(await utils.isPresent('#prejoin-container')).to.be.true;
// Clicking to join button
await utils.waitForElement('#join-button');
@ -57,7 +55,24 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onTokenRequested has been received
await utils.waitForElement('#onTokenRequested');
expect(await utils.isPresent('#onTokenRequested')).toBeTrue();
expect(await utils.isPresent('#onTokenRequested')).to.be.true;
});
it('should receive the onRoomDisconnected event', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Clicking to leave button
const leaveButton = await utils.waitForElement('#leave-btn');
expect(await utils.isPresent('#leave-btn')).to.be.true;
await leaveButton.click();
// Checking if onRoomDisconnected has been received
await utils.waitForElement('#onRoomDisconnected');
expect(await utils.isPresent('#onRoomDisconnected')).to.be.true;
});
it('should receive the onVideoEnabledChanged event when clicking on the prejoin', async () => {
@ -69,7 +84,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onVideoEnabledChanged has been received
await utils.waitForElement('#onVideoEnabledChanged-false');
expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onVideoEnabledChanged-false')).to.be.true;
});
it('should receive the onVideoEnabledChanged event when clicking on the toolbar', async () => {
@ -85,11 +100,11 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onVideoEnabledChanged has been received
await utils.waitForElement('#onVideoEnabledChanged-false');
expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onVideoEnabledChanged-false')).to.be.true;
await utils.clickOn('#camera-btn');
await utils.waitForElement('#onVideoEnabledChanged-true');
expect(await utils.isPresent('#onVideoEnabledChanged-true')).toBeTrue();
expect(await utils.isPresent('#onVideoEnabledChanged-true')).to.be.true;
});
it('should receive the onVideoEnabledChanged event when clicking on the settings panel', async () => {
@ -108,11 +123,11 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('ov-video-devices-select #camera-button');
// Checking if onVideoEnabledChanged has been received
await utils.waitForElement('#onVideoEnabledChanged-false');
expect(await utils.isPresent('#onVideoEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onVideoEnabledChanged-false')).to.be.true;
await utils.clickOn('ov-video-devices-select #camera-button');
await utils.waitForElement('#onVideoEnabledChanged-true');
expect(await utils.isPresent('#onVideoEnabledChanged-true')).toBeTrue();
expect(await utils.isPresent('#onVideoEnabledChanged-true')).to.be.true;
});
it('should receive the onVideoDeviceChanged event on prejoin', async () => {
@ -126,7 +141,7 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('#option-custom_fake_video_1');
await utils.waitForElement('#onVideoDeviceChanged');
expect(await utils.isPresent('#onVideoDeviceChanged')).toBeTrue();
expect(await utils.isPresent('#onVideoDeviceChanged')).to.be.true;
});
it('should receive the onVideoDeviceChanged event on settings panel', async () => {
@ -149,7 +164,7 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('#option-custom_fake_video_1');
await utils.waitForElement('#onVideoDeviceChanged');
expect(await utils.isPresent('#onVideoDeviceChanged')).toBeTrue();
expect(await utils.isPresent('#onVideoDeviceChanged')).to.be.true;
});
it('should receive the onAudioEnabledChanged event when clicking on the prejoin', async () => {
@ -161,7 +176,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onAudioEnabledChanged has been received
await utils.waitForElement('#onAudioEnabledChanged-false');
expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onAudioEnabledChanged-false')).to.be.true;
});
it('should receive the onAudioEnabledChanged event when clicking on the toolbar', async () => {
@ -177,11 +192,11 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onAudioEnabledChanged has been received
await utils.waitForElement('#onAudioEnabledChanged-false');
expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onAudioEnabledChanged-false')).to.be.true;
await utils.clickOn('#mic-btn');
await utils.waitForElement('#onAudioEnabledChanged-true');
expect(await utils.isPresent('#onAudioEnabledChanged-true')).toBeTrue();
expect(await utils.isPresent('#onAudioEnabledChanged-true')).to.be.true;
});
it('should receive the onAudioEnabledChanged event when clicking on the settings panel', async () => {
@ -200,11 +215,11 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('ov-audio-devices-select #microphone-button');
// Checking if onAudioEnabledChanged has been received
await utils.waitForElement('#onAudioEnabledChanged-false');
expect(await utils.isPresent('#onAudioEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onAudioEnabledChanged-false')).to.be.true;
await utils.clickOn('ov-audio-devices-select #microphone-button');
await utils.waitForElement('#onAudioEnabledChanged-true');
expect(await utils.isPresent('#onAudioEnabledChanged-true')).toBeTrue();
expect(await utils.isPresent('#onAudioEnabledChanged-true')).to.be.true;
});
it('should receive the onAudioDeviceChanged event on prejoin', async () => {
@ -218,7 +233,7 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('#option-custom_fake_audio_1');
await utils.waitForElement('#onAudioDeviceChanged');
expect(await utils.isPresent('#onAudioDeviceChanged')).toBeTrue();
expect(await utils.isPresent('#onAudioDeviceChanged')).to.be.true;
});
it('should receive the onAudioDeviceChanged event on settings panel', async () => {
@ -241,7 +256,7 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('#option-custom_fake_audio_1');
await utils.waitForElement('#onAudioDeviceChanged');
expect(await utils.isPresent('#onAudioDeviceChanged')).toBeTrue();
expect(await utils.isPresent('#onAudioDeviceChanged')).to.be.true;
});
it('should receive the onLangChanged event on prejoin', async () => {
@ -256,7 +271,7 @@ describe('Testing videoconference EVENTS', () => {
await browser.sleep(500);
await utils.waitForElement('#onLangChanged-es');
expect(await utils.isPresent('#onLangChanged-es')).toBeTrue();
expect(await utils.isPresent('#onLangChanged-es')).to.be.true;
});
it('should receive the onLangChanged event on settings panel', async () => {
@ -277,7 +292,7 @@ describe('Testing videoconference EVENTS', () => {
await browser.sleep(500);
await utils.waitForElement('#onLangChanged-es');
expect(await utils.isPresent('#onLangChanged-es')).toBeTrue();
expect(await utils.isPresent('#onLangChanged-es')).to.be.true;
});
it('should receive the onScreenShareEnabledChanged event', async () => {
@ -289,16 +304,16 @@ describe('Testing videoconference EVENTS', () => {
// Clicking to leave button
const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
expect(await utils.isPresent('#screenshare-btn')).to.be.true;
await screenshareButton.click();
// Checking if onScreenShareEnabledChanged has been received
await utils.waitForElement('#onScreenShareEnabledChanged');
expect(await utils.isPresent('#onScreenShareEnabledChanged')).toBeTrue();
expect(await utils.isPresent('#onScreenShareEnabledChanged')).to.be.true;
});
// With headless mode, the Fullscreen API doesn't work
it('should receive the onFullscreenEnabledChanged event', async () => {
(isHeadless ? it.skip : it)('should receive the onFullscreenEnabledChanged event', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
@ -311,13 +326,13 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onFullscreenEnabledChanged has been received
await utils.waitForElement('#onFullscreenEnabledChanged-true');
expect(await utils.isPresent('#onFullscreenEnabledChanged-true')).toBeTrue();
expect(await utils.isPresent('#onFullscreenEnabledChanged-true')).to.be.true;
await (await utils.waitForElement('html')).sendKeys(Key.F11);
await browser.sleep(500);
await utils.waitForElement('#onFullscreenEnabledChanged-false');
expect(await utils.isPresent('#onFullscreenEnabledChanged-false')).toBeTrue();
expect(await utils.isPresent('#onFullscreenEnabledChanged-false')).to.be.true;
});
it('should receive the onChatPanelStatusChanged event', async () => {
@ -331,13 +346,13 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onChatPanelStatusChanged has been received
await utils.waitForElement('#onChatPanelStatusChanged-true');
expect(await utils.isPresent('#onChatPanelStatusChanged-true')).toBeTrue();
expect(await utils.isPresent('#onChatPanelStatusChanged-true')).to.be.true;
await utils.togglePanel('chat');
// Checking if onChatPanelStatusChanged has been received
await utils.waitForElement('#onChatPanelStatusChanged-false');
expect(await utils.isPresent('#onChatPanelStatusChanged-false')).toBeTrue();
expect(await utils.isPresent('#onChatPanelStatusChanged-false')).to.be.true;
});
it('should receive the onParticipantsPanelStatusChanged event', async () => {
@ -351,13 +366,13 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onParticipantsPanelStatusChanged has been received
await utils.waitForElement('#onParticipantsPanelStatusChanged-true');
expect(await utils.isPresent('#onParticipantsPanelStatusChanged-true')).toBeTrue();
expect(await utils.isPresent('#onParticipantsPanelStatusChanged-true')).to.be.true;
await utils.togglePanel('participants');
// Checking if onParticipantsPanelStatusChanged has been received
await utils.waitForElement('#onParticipantsPanelStatusChanged-false');
expect(await utils.isPresent('#onParticipantsPanelStatusChanged-false')).toBeTrue();
expect(await utils.isPresent('#onParticipantsPanelStatusChanged-false')).to.be.true;
});
it('should receive the onActivitiesPanelStatusChanged event', async () => {
@ -370,13 +385,13 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onActivitiesPanelStatusChanged has been received
await utils.waitForElement('#onActivitiesPanelStatusChanged-true');
expect(await utils.isPresent('#onActivitiesPanelStatusChanged-true')).toBeTrue();
expect(await utils.isPresent('#onActivitiesPanelStatusChanged-true')).to.be.true;
await utils.togglePanel('activities');
// Checking if onActivitiesPanelStatusChanged has been received
await utils.waitForElement('#onActivitiesPanelStatusChanged-false');
expect(await utils.isPresent('#onActivitiesPanelStatusChanged-false')).toBeTrue();
expect(await utils.isPresent('#onActivitiesPanelStatusChanged-false')).to.be.true;
});
it('should receive the onSettingsPanelStatusChanged event', async () => {
@ -389,13 +404,13 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onSettingsPanelStatusChanged has been received
await utils.waitForElement('#onSettingsPanelStatusChanged-true');
expect(await utils.isPresent('#onSettingsPanelStatusChanged-true')).toBeTrue();
expect(await utils.isPresent('#onSettingsPanelStatusChanged-true')).to.be.true;
await utils.togglePanel('settings');
// Checking if onSettingsPanelStatusChanged has been received
await utils.waitForElement('#onSettingsPanelStatusChanged-false');
expect(await utils.isPresent('#onSettingsPanelStatusChanged-false')).toBeTrue();
expect(await utils.isPresent('#onSettingsPanelStatusChanged-false')).to.be.true;
});
it('should receive the onRecordingStartRequested event when clicking toolbar button', async () => {
@ -409,7 +424,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onRecordingStartRequested has been received
await utils.waitForElement(`#onRecordingStartRequested-${roomName}`);
expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).toBeTrue();
expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).to.be.true;
});
xit('should receive the onRecordingStopRequested event when clicking toolbar button', async () => {});
@ -443,7 +458,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onBroadcastingStopRequested has been received
await utils.waitForElement('#onBroadcastingStopRequested');
expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue();
expect(await utils.isPresent('#onBroadcastingStopRequested')).to.be.true;
});
it('should receive the onRecordingStartRequested when clicking from activities panel', async () => {
@ -469,7 +484,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onRecordingStartRequested has been received
await utils.waitForElement(`#onRecordingStartRequested-${roomName}`);
expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).toBeTrue();
expect(await utils.isPresent(`#onRecordingStartRequested-${roomName}`)).to.be.true;
});
xit('should receive the onRecordingStopRequested when clicking from activities panel', async () => {});
@ -485,7 +500,7 @@ describe('Testing videoconference EVENTS', () => {
// Clicking to activities button
const activitiesButton = await utils.waitForElement('#activities-panel-btn');
expect(await utils.isPresent('#activities-panel-btn')).toBeTrue();
expect(await utils.isPresent('#activities-panel-btn')).to.be.true;
await activitiesButton.click();
await browser.sleep(1500);
@ -497,15 +512,15 @@ describe('Testing videoconference EVENTS', () => {
// Delete event
element = await utils.waitForElement('#delete-recording-btn');
expect(await utils.isPresent('#delete-recording-btn')).toBeTrue();
expect(await utils.isPresent('#delete-recording-btn')).to.be.true;
await element.click();
element = await utils.waitForElement('#delete-recording-confirm-btn');
expect(await utils.isPresent('#delete-recording-confirm-btn')).toBeTrue();
expect(await utils.isPresent('#delete-recording-confirm-btn')).to.be.true;
await element.click();
await utils.waitForElement(`#onRecordingDeleteRequested-${roomName}-fakeRecording`);
expect(await utils.isPresent(`#onRecordingDeleteRequested-${roomName}-fakeRecording`)).toBeTrue();
expect(await utils.isPresent(`#onRecordingDeleteRequested-${roomName}-fakeRecording`)).to.be.true;
});
it('should receive the onBroadcastingStartRequested event when clicking from panel', async () => {
@ -525,7 +540,7 @@ describe('Testing videoconference EVENTS', () => {
await browser.sleep(1000);
const button = await utils.waitForElement('#broadcasting-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
const input = await utils.waitForElement('#broadcast-url-input');
await input.sendKeys(broadcastUrl);
@ -534,7 +549,7 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onBroadcastingStartRequested has been received
await utils.waitForElement(`#onBroadcastingStartRequested-${roomName}-${broadcastUrl}`);
expect(await utils.isPresent(`#onBroadcastingStartRequested-${roomName}-${broadcastUrl}`)).toBeTrue();
expect(await utils.isPresent(`#onBroadcastingStartRequested-${roomName}-${broadcastUrl}`)).to.be.true;
});
xit('should receive the onBroadcastingStopRequested event when clicking from panel', async () => {
@ -550,21 +565,21 @@ describe('Testing videoconference EVENTS', () => {
await utils.clickOn('#broadcasting-activity');
const button = await utils.waitForElement('#broadcasting-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
const input = await utils.waitForElement('#broadcast-url-input');
await input.sendKeys('BroadcastUrl');
await utils.clickOn('#broadcasting-btn');
expect(await utils.isPresent('#broadcasting-tag')).toBeTrue();
expect(await utils.isPresent('#broadcasting-tag')).to.be.true;
await utils.clickOn('#stop-broadcasting-btn');
// Checking if onBroadcastingStopRequested has been received
await utils.waitForElement('#onBroadcastingStopRequested');
expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue();
expect(await utils.isPresent('#broadcasting-tag')).toBeFalse();
expect(await utils.isPresent('#onBroadcastingStopRequested')).to.be.true;
expect(await utils.isPresent('#broadcasting-tag')).to.be.false;
});
xit('should receive the onBroadcastingStopRequested event when clicking from toolbar', async () => {
@ -582,8 +597,8 @@ describe('Testing videoconference EVENTS', () => {
// Checking if onBroadcastingStopRequested has been received
await utils.waitForElement('#onBroadcastingStopRequested');
expect(await utils.isPresent('#onBroadcastingStopRequested')).toBeTrue();
expect(await utils.isPresent('#broadcasting-tag')).toBeFalse();
expect(await utils.isPresent('#onBroadcastingStopRequested')).to.be.true;
expect(await utils.isPresent('#broadcasting-tag')).to.be.false;
});
it('should receive the onRoomCreated event', async () => {
@ -594,53 +609,36 @@ describe('Testing videoconference EVENTS', () => {
await utils.checkToolbarIsPresent();
await utils.waitForElement('#onRoomCreated');
expect(await utils.isPresent('#onRoomCreated')).toBeTrue();
expect(await utils.isPresent('#onRoomCreated')).to.be.true;
expect(await utils.isPresent('#onReadyToJoin')).toBeFalse();
expect(await utils.isPresent('#onReadyToJoin')).to.be.false;
});
// PARTICIPANT EVENTS
// * PUBLISHER EVENTS
it('should receive onParticipantCreated event from LOCAL participant', async () => {
const participantName = 'TEST_USER';
await browser.get(`${url}&participantName=${participantName}&prejoin=false`);
await utils.waitForElement(`#${participantName}-onParticipantCreated`);
expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).toBeTrue();
expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).to.be.true;
});
it('should receive the onParticipantLeft event', async () => {
await browser.get(`${url}&prejoin=false&redirectToHome=false`);
// * ROOM EVENTS
it('should receive roomDisconnected event from LOCAL participant', async () => {
const participantName = 'TEST_USER';
let element;
await browser.get(`${url}&prejoin=false&participantName=${participantName}`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Clicking to leave button
const leaveButton = await utils.waitForElement('#leave-btn');
expect(await utils.isPresent('#leave-btn')).toBeTrue();
await leaveButton.click();
// Checking if leave button is not present
element = await utils.waitForElement('#leave-btn');
await element.click();
await utils.waitForElement('#events');
// Checking if onParticipantLeft has been received
await utils.waitForElement('#onParticipantLeft');
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
await utils.waitForElement(`#roomDisconnected`);
expect(await utils.isPresent(`#roomDisconnected`)).to.be.true;
});
// * ROOM EVENTS
//TODO: Implement a mechanism to emulate network disconnection
// it('should receive the onRoomDisconnected event', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// await utils.checkToolbarIsPresent();
// // Emulate network disconnection
// await utils.forceCloseWebsocket();
// // Checking if onRoomDisconnected has been received
// await utils.waitForElement('#onRoomDisconnected');
// expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
// });
});

View File

@ -1,18 +1,20 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { getBrowserOptionsWithoutDevices, TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Media Devices: Virtual Device Replacement and Permissions Handling', () => {
describe('Testing replace track with emulated devices', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -22,99 +24,118 @@ describe('Media Devices: Virtual Device Replacement and Permissions Handling', (
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
await browser.quit();
});
it('should allow selecting and replacing the video track with a custom virtual device in the prejoin page', async () => {
it('should replace the video track in prejoin page', async () => {
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&fakeDevices=true`);
let videoDevices = await utils.waitForElement('#video-devices-form');
await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click();
let videoLabel;
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1');
await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click();
let videoLabel;
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).to.be.equal('custom_fake_video_1');
await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0');
await element.click();
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0');
expect(videoLabel).to.be.equal('fake_device_0');
});
it('should allow selecting and replacing the video track with a custom virtual device in the videoconference page', async () => {
it('should replace the video track in videoconference page', async () => {
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
await browser.sleep(500);
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
expect(await utils.isPresent('ov-video-devices-select')).to.be.true;
let videoDevices = await utils.waitForElement('#video-devices-form');
await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click();
let videoLabel;
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1');
expect(videoLabel).to.be.equal('custom_fake_video_1');
await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0');
await element.click();
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0');
expect(videoLabel).to.be.equal('fake_device_0');
});
it('should replace the screen track with a custom virtual device', async () => {
const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
// TODO: Uncommented when Livekit allows to replace the screen track
// it('should replace the screen track', async () => {
// const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
// await browser.get(`${url}&prejoin=false&fakeDevices=true`);
await utils.checkLayoutPresent();
await utils.checkToolbarIsPresent();
// await utils.checkLayoutPresent();
// await utils.checkToolbarIsPresent();
await utils.clickOn('#screenshare-btn');
// await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
// await browser.sleep(500);
let screenLabel = await browser.executeScript<string>(script);
expect(screenLabel).not.toEqual('custom_fake_screen');
// let screenLabel = await browser.executeScript<string>(script);
// expect(screenLabel).not.equal('custom_fake_screen');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
// await utils.clickOn('#video-settings-btn-SCREEN');
// await browser.sleep(500);
await utils.waitForElement('#replace-screen-button');
await utils.clickOn('#replace-screen-button');
await browser.sleep(1000);
// await utils.waitForElement('.video-settings-menu');
// const replaceBtn = await utils.waitForElement('#replace-screen-button');
// await replaceBtn.sendKeys(Key.ENTER);
screenLabel = await browser.executeScript<string>(script);
expect(screenLabel).toEqual('custom_fake_screen');
});
// await browser.sleep(1000);
// screenLabel = await browser.executeScript<string>(script);
// expect(screenLabel).to.be.equal('custom_fake_screen');
// });
});
describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
describe('Testing WITHOUT MEDIA DEVICES permissions', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(getBrowserOptionsWithoutDevices())
.usingServer(TestAppConfig.seleniumAddress)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -124,57 +145,75 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should disable camera and microphone buttons in the prejoin page when permissions are denied', async () => {
it('should be able to ACCESS to PREJOIN page', async () => {
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
let button = await utils.waitForElement('#camera-button');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
button = await utils.waitForElement('#microphone-button');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
});
it('should disable camera and microphone buttons in the room page when permissions are denied', async () => {
it('should be able to ACCESS to ROOM page', async () => {
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
});
it('should disable camera and microphone buttons in the room page without prejoin when permissions are denied', async () => {
it('should be able to ACCESS to ROOM page without prejoin', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
});
it('should disable camera and microphone device selection buttons in settings when permissions are denied', async () => {
it('should the settings buttons be disabled', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.togglePanel('settings');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
expect(await utils.isPresent('ov-video-devices-select')).to.be.true;
let button = await utils.waitForElement('#camera-button');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
await utils.clickOn('#audio-opt');
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
expect(await utils.isPresent('ov-audio-devices-select')).to.be.true;
button = await utils.waitForElement('#microphone-button');
expect(await button.isEnabled()).toBeFalse();
expect(await button.isEnabled()).to.be.false;
});
});

View File

@ -1,19 +1,21 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { expect } from 'chai';
import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { OPENVIDU_CALL_SERVER } from '../config';
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Panels: UI Navigation and Section Switching', () => {
describe('Testing panels', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -23,9 +25,6 @@ describe('Panels: UI Navigation and Section Switching', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
@ -37,130 +36,190 @@ describe('Panels: UI Navigation and Section Switching', () => {
// let element;
// await browser.get(`${url}`);
// element = await utils.waitForElement('#pre-join-container');
// expect(await utils.isPresent('#pre-join-container')).toBeTrue();
// expect(await utils.isPresent('#pre-join-container')).to.be.true;
// const backgroundButton = await utils.waitForElement('#background-effects-btn');
// expect(await utils.isPresent('#background-effects-btn')).toBeTrue();
// expect(await backgroundButton.isEnabled()).toBeTrue();
// expect(await utils.isPresent('#background-effects-btn')).to.be.true;
// expect(await backgroundButton.isEnabled()).to.be.true;
// await backgroundButton.click();
// await browser.sleep(500);
// await utils.waitForElement('#background-effects-container');
// expect(await utils.isPresent('#background-effects-container')).toBeTrue();
// expect(await utils.isPresent('#background-effects-container')).to.be.true;
// element = await utils.waitForElement('#camera-button');
// expect(await utils.isPresent('#camera-button')).toBeTrue();
// expect(await element.isEnabled()).toBeTrue();
// expect(await utils.isPresent('#camera-button')).to.be.true;
// expect(await element.isEnabled()).to.be.true;
// await element.click();
// await browser.sleep(500);
// element = await utils.waitForElement('#video-poster');
// expect(await utils.isPresent('#video-poster')).toBeTrue();
// expect(await utils.isPresent('#video-poster')).to.be.true;
// expect(await backgroundButton.isDisplayed()).toBeTrue();
// expect(await backgroundButton.isEnabled()).toBeFalse();
// expect(await backgroundButton.isDisplayed()).to.be.true;
// expect(await backgroundButton.isEnabled()).to.be.false;
// expect(await utils.isPresent('#background-effects-container')).toBeFalse();
// expect(await utils.isPresent('#background-effects-container')).to.be.false;
// });
it('should open and close the CHAT panel and verify its content', async () => {
it('should toggle CHAT panel', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.input-container')).to.be.true;
await utils.waitForElement('.messages-container');
expect(await utils.isPresent('.messages-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).to.be.true;
await chatButton.click();
expect(await utils.isPresent('.input-container')).toBeFalse();
expect(await utils.isPresent('.messages-container')).toBeFalse();
expect(await utils.isPresent('.input-container')).to.be.false;
expect(await utils.isPresent('.messages-container')).to.be.false;
});
it('should open and close the PARTICIPANTS panel and verify its content', async () => {
it('should toggle PARTICIPANTS panel', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.local-participant-container');
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
expect(await utils.isPresent('.local-participant-container')).to.be.true;
await utils.waitForElement('ov-participant-panel-item');
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
expect(await utils.isPresent('ov-participant-panel-item')).to.be.true;
await participantBtn.click();
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
expect(await utils.isPresent('.local-participant-container')).to.be.false;
expect(await utils.isPresent('ov-participant-panel-item')).to.be.false;
});
it('should open and close the ACTIVITIES panel and verify its content', async () => {
it('should toggle ACTIVITIES panel', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Get activities button and click into it
const activitiesBtn = await utils.waitForElement('#activities-panel-btn');
await activitiesBtn.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).toBeTrue();
expect(await utils.isPresent('#activities-container')).to.be.true;
await utils.waitForElement('#recording-activity');
expect(await utils.isPresent('#recording-activity')).toBeTrue();
expect(await utils.isPresent('#recording-activity')).to.be.true;
await activitiesBtn.click();
expect(await utils.isPresent('#activities-container')).toBeFalse();
expect(await utils.isPresent('#recording-activity')).toBeFalse();
expect(await utils.isPresent('#activities-container')).to.be.false;
expect(await utils.isPresent('#recording-activity')).to.be.false;
});
it('should open the SETTINGS panel and verify its content', async () => {
it('should toggle SETTINGS panel', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
element = await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('#default-settings-panel')).toBeTrue();
expect(await utils.isPresent('#default-settings-panel')).to.be.true;
});
it('should switch between PARTICIPANTS and CHAT panels and verify correct content is shown', async () => {
it('should switching between PARTICIPANTS and CHAT panels', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Open chat panel
const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
expect(await utils.isPresent('.sidenav-menu')).to.be.true;
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue();
expect(await utils.isPresent('.input-container')).to.be.true;
expect(await utils.isPresent('.messages-container')).to.be.true;
// Open participants panel
const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
expect(await utils.isPresent('.local-participant-container')).to.be.true;
expect(await utils.isPresent('ov-participant-panel-item')).to.be.true;
// Switch to chat panel
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue();
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
expect(await utils.isPresent('.input-container')).to.be.true;
expect(await utils.isPresent('.messages-container')).to.be.true;
expect(await utils.isPresent('.local-participant-container')).to.be.false;
expect(await utils.isPresent('ov-participant-panel-item')).to.be.false;
// Close chat panel
await chatButton.click();
expect(await utils.getNumberOfElements('.input-container')).toEqual(0);
expect(await utils.isPresent('messages-container')).toBeFalse();
expect(await utils.getNumberOfElements('.input-container')).equals(0);
expect(await utils.isPresent('messages-container')).to.be.false;
});
it('should switch between sections in the SETTINGS panel and verify correct content is shown', async () => {
it('should switching between sections in SETTINGS PANEL', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
await utils.checkToolbarIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.togglePanel('settings');
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
await browser.sleep(500);
expect(await utils.isPresent('.sidenav-menu')).to.be.true;
// Check if general section is shown
element = await utils.waitForElement('#general-opt');
await element.click();
expect(await utils.isPresent('ov-participant-name-input')).toBeTrue();
expect(await utils.isPresent('ov-participant-name-input')).to.be.true;
// Check if video section is shown
element = await utils.waitForElement('#video-opt');
await element.click();
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
expect(await utils.isPresent('ov-video-devices-select')).to.be.true;
// Check if audio section is shown
element = await utils.waitForElement('#audio-opt');
await element.click();
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
expect(await utils.isPresent('ov-audio-devices-select')).to.be.true;
});
});

View File

@ -1,19 +1,20 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('E2E: Screensharing features', () => {
describe('Testing screenshare features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -23,188 +24,197 @@ describe('E2E: Screensharing features', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should toggle screensharing on and off twice, updating video count', async () => {
it('should toggle screensharing twice', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Enable screensharing
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('video')).equals(2);
expect(await utils.getNumberOfElements('.OV_stream.speaking')).equals(1);
// Disable screensharing
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
// Enable again
// Clicking to screensharing button
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
// Disable again
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('video')).equals(1);
// toggle screenshare again
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).equals(2);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).equals(1);
});
it('should show screenshare and muted camera (camera off, screenshare on)', async () => {
it('should show screen and muted camera', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Mute camera
await utils.waitForElement('#camera-btn');
await utils.clickOn('#camera-btn');
// Enable screensharing
// Clicking to screensharing button
const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue();
expect(await screenshareButton.isDisplayed()).to.be.true;
await screenshareButton.click();
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('video')).equals(2);
// Disable screensharing
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).equals(1);
});
it('should display screensharing with a single pinned video', async () => {
it('should screensharing with PINNED video', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Enable screensharing
// Clicking to screensharing button
const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue();
expect(await screenshareButton.isDisplayed()).to.be.true;
await screenshareButton.click();
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
});
it('should replace pinned video when a second participant starts screensharing', async () => {
it('should screensharing with PINNED video and replace the existing one', async () => {
const roomName = 'screensharingE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
// First participant screenshares
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
// Second participant joins and screenshares
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
expect(await utils.getNumberOfElements('video')).equals(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
// Switch back to first tab and check
// Go to first tab
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
expect(await utils.getNumberOfElements('video')).equals(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
});
it('should unpin screensharing and restore previous pinned video when disabled', async () => {
it('should disabled a screensharing and pinned the previous one', async () => {
const roomName = 'screensharingtwoE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
// First participant screenshares
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
// Second participant joins and screenshares
// Starting new browser for adding the second participant
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
expect(await utils.getNumberOfElements('video')).equals(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Disable screensharing for second participant
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Switch back to first tab and check
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
});
it('should correctly share screen with microphone muted and maintain proper track state', async () => {
// Helper for inspecting stream tracks
const getMediaTracks = (className: string) => {
return `
const tracks = document.getElementsByClassName('${className}')[0].srcObject.getTracks();
return tracks.map(track => ({
kind: track.kind,
enabled: track.enabled,
id: track.id,
label: track.label
}));`;
};
// Setup: Navigate to room and skip prejoin
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Step 1: First mute the microphone
const micButton = await utils.waitForElement('#mic-btn');
await micButton.click();
// Step 2: Start screen sharing
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
// Disable screensharing
await utils.clickOn('#screenshare-btn');
// Step 3: Verify both streams are present
await utils.waitForElement('.screen-type');
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Step 4: Verify screen share track properties
const screenTracks: any[] = await browser.executeScript(getMediaTracks('screen-type'));
expect(screenTracks.length).toEqual(1);
expect(screenTracks[0].kind).toEqual('video');
expect(screenTracks[0].enabled).toBeTrue();
// Step 5: Verify microphone status indicators for both streams
// await utils.waitForElement('#status-mic');
// const micStatusCount = await utils.getNumberOfElements('#status-mic');
// expect(micStatusCount).toEqual(2);
// Step 6: Stop screen sharing and verify stream count
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('video')).equals(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
// Go to first tab
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).equals(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).equals(1);
});
// it('should screensharing with audio muted', async () => {
// let isAudioEnabled;
// const getAudioScript = (className: string) => {
// return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
// };
// await browser.get(`${url}&prejoin=false`);
// await utils.checkLayoutPresent();
// const micButton = await utils.waitForElement('#mic-btn');
// await micButton.click();
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// expect(await utils.isPresent('#screenshare-btn')).to.be.true;
// await screenshareButton.click();
// await utils.waitForElement('.screen-type');
// expect(await utils.getNumberOfElements('video')).equals(2);
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
// expect(isAudioEnabled).to.be.false;
// await utils.waitForElement('#status-mic');
// expect(await utils.getNumberOfElements('#status-mic')).equals(2);
// // Clicking to screensharing button
// await screenshareButton.click();
// expect(await utils.getNumberOfElements('video')).equals(1);
// });
// it('should show and hide CAMERA stream when muting video with screensharing', async () => {
// await browser.get(`${url}&prejoin=false`);
@ -212,16 +222,16 @@ describe('E2E: Screensharing features', () => {
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// expect(await screenshareButton.isDisplayed()).toBeTrue();
// expect(await screenshareButton.isDisplayed()).to.be.true;
// await screenshareButton.click();
// await utils.waitForElement('.OV_big');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('video')).equals(2);
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// expect(await utils.getNumberOfElements('video')).equals(1);
// });
// it('should screenshare has audio active when camera is muted', async () => {
@ -234,33 +244,33 @@ describe('E2E: Screensharing features', () => {
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
// expect(await utils.isPresent('#screenshare-btn')).to.be.true;
// await screenshareButton.click();
// await utils.waitForElement('.OV_big');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// expect(await utils.getNumberOfElements('video')).equals(2);
// expect(await utils.getNumberOfElements('#status-mic')).equals(1);
// // Muting camera video
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// expect(await utils.getNumberOfElements('video')).equals(1);
// await browser.sleep(500);
// expect(await utils.isPresent('#status-mic')).toBeFalse();
// expect(await utils.isPresent('#status-mic')).to.be.false;
// // Checking if audio is muted after join the room
// isAudioEnabled = await browser.executeScript(audioEnableScript);
// expect(isAudioEnabled).toBeTrue();
// expect(isAudioEnabled).to.be.true;
// // Unmuting camera
// await muteVideoButton.click();
// await browser.sleep(1000);
// await utils.waitForElement('.camera-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// expect(await utils.getNumberOfElements('video')).equals(2);
// expect(await utils.getNumberOfElements('#status-mic')).equals(1);
// });
// it('should camera come back with audio muted when screensharing', async () => {
@ -279,38 +289,38 @@ describe('E2E: Screensharing features', () => {
// await screenshareButton.click();
// await utils.waitForElement('.screen-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// expect(await utils.getNumberOfElements('video')).equals(2);
// expect(await utils.getNumberOfElements('#status-mic')).equals(1);
// // Mute camera
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// expect(await utils.isPresent('#status-mic')).toBeFalse();
// expect(await utils.getNumberOfElements('video')).equals(1);
// expect(await utils.isPresent('#status-mic')).to.be.false;
// // Checking if audio is muted after join the room
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
// expect(isAudioEnabled).toBeTrue();
// expect(isAudioEnabled).to.be.true;
// // Mute audio
// const muteAudioButton = await utils.waitForElement('#mic-btn');
// await muteAudioButton.click();
// await utils.waitForElement('#status-mic');
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// expect(await utils.getNumberOfElements('#status-mic')).equals(1);
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
// expect(isAudioEnabled).toBeFalse();
// expect(isAudioEnabled).to.be.false;
// // Unmute camera
// await muteVideoButton.click();
// await utils.waitForElement('.camera-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(2);
// expect(await utils.getNumberOfElements('video')).equals(2);
// expect(await utils.getNumberOfElements('#status-mic')).equals(2);
// isAudioEnabled = await browser.executeScript(getAudioScript('camera-type'));
// expect(isAudioEnabled).toBeFalse();
// expect(isAudioEnabled).to.be.false;
// });
});

View File

@ -1,18 +1,20 @@
import { expect } from 'chai';
import { Builder, ILocation, IRectangle, ISize, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Stream rendering and media toggling scenarios', () => {
describe('Checking stream elements by disabling/enabling the media', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -22,52 +24,10 @@ describe('Stream rendering and media toggling scenarios', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should not render any video element when joining with video disabled', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
});
it('should render a video element but no audio when joining with audio muted', async () => {
await browser.get(`${url}&prejoin=true&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should render both video and audio elements when joining with both enabled', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
});
it('should add a screen share video/audio when sharing screen with both camera and mic muted', async () => {
it('should show ONE video element when a LOCAL participant joins with VIDEO and AUDIO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
@ -75,24 +35,32 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(1); //screen sharse video
expect(await utils.getNumberOfElements('audio')).toEqual(1); //screen share audio
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should add a screen share video/audio when sharing screen with both camera and mic enabled', async () => {
it('should show ONE video element when a LOCAL participant joins with AUDIO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should show a video element when a LOCAL participant joins with VIDEO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should show a video element when a LOCAL participant joins', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
@ -100,26 +68,104 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should show a video element when a LOCAL participant shares its screen with VIDEO and AUDIO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); //screen share audio and local audio
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
/* ------------ Checking video/audio elements with two participants ------------ */
it('should show a video element when a LOCAL participant shares its screen with VIDEO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=true`);
it('should not render any video/audio elements when two participants join with both video and audio muted', async () => {
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should show a video element when a LOCAL participant shares its screen with AUDIO MUTED', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
it('should show a video element when a LOCAL participant shares its screen', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
});
/* ------------ Checking video elements with two participants ------------ */
it('should show TWO video elements when a REMOTE participant joins with VIDEO and AUDIO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
@ -127,27 +173,21 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
await utils.waitForElement('.OV_stream.remote');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should render two video elements and no audio when two participants join with audio muted', async () => {
it('should show TWO video elements when a REMOTE participant joins with AUDIO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`;
await browser.get(fixedUrl);
@ -155,29 +195,21 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
await utils.waitForElement('.OV_stream.remote');
await browser.sleep(2000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should not render any video elements but should render two audio elements when two participants join with video disabled', async () => {
it('should show TWO video elements when a REMOTE participant joins with VIDEO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false`;
await browser.get(fixedUrl);
@ -185,28 +217,41 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
await utils.waitForElement('.OV_stream.remote');
await browser.sleep(2000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should add a screen share video/audio when a participant with both video and audio muted shares their screen (two participants)', async () => {
it('should show TWO video elements when a REMOTE participant joins', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).equal(1);
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
await utils.waitForElement('.OV_stream.remote');
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should show THREE video elements when a REMOTE participant shares its screen with AUDIO and VIDEO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
@ -222,30 +267,96 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[1]);
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should add a screen share video/audio when a remote participant with both video and audio enabled shares their screen', async () => {
it('should show THREE video elements when a REMOTE participant shares its screen with VIDEO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=true`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[1]);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should show THREE video elements when a REMOTE participant shares its screen with AUDIO MUTED', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[1]);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should show THREE video elements when a REMOTE participant shares its screen', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`;
await browser.get(fixedUrl);
@ -261,30 +372,26 @@ describe('Stream rendering and media toggling scenarios', () => {
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(3);
expect(await utils.getNumberOfElements('audio')).toEqual(3); // screen share audios and local audio and remote audio
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(3);
expect(await utils.getNumberOfElements('audio')).toEqual(3); // screen share audios and local audio and remote audio
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[1]);
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
expect(await utils.getNumberOfElements('.OV_stream')).equal(2);
});
it('should add a screen share video/audio for both participants when both share their screen with video/audio muted', async () => {
it('should show FOUR video elements when a two participant shares theirs screen', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
@ -298,45 +405,39 @@ describe('Stream rendering and media toggling scenarios', () => {
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(4);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(4);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(4);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); // screen share audios
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(4);
await utils.clickOn('#camera-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#screenshare-menu');
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
await browser.switchTo().window(tabs[1]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
expect(await utils.getNumberOfElements('.OV_stream')).equal(3);
});
});
describe('Stream UI controls and interaction features', () => {
describe('Testing stream features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -346,9 +447,6 @@ describe('Stream UI controls and interaction features', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
@ -359,7 +457,7 @@ describe('Stream UI controls and interaction features', () => {
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#pin-btn');
expect(await utils.isPresent('#pin-btn')).toBeTrue();
expect(await utils.isPresent('#pin-btn')).to.be.true;
});
it('should show the PIN button over the REMOTE video', async () => {
@ -380,7 +478,7 @@ describe('Stream UI controls and interaction features', () => {
await utils.waitForElement('.OV_stream.remote');
await utils.hoverOn('.OV_stream.remote');
await utils.waitForElement('#pin-btn');
expect(await utils.isPresent('#pin-btn')).toBeTrue();
expect(await utils.isPresent('#pin-btn')).to.be.true;
});
it('should show the SILENCE button ONLY over the REMOTE video', async () => {
@ -393,7 +491,7 @@ describe('Stream UI controls and interaction features', () => {
await utils.hoverOn('.OV_stream.local');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).equals(0);
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
@ -405,13 +503,13 @@ describe('Stream UI controls and interaction features', () => {
await utils.waitForElement('.OV_stream.remote');
await utils.hoverOn('.OV_stream.remote');
await utils.waitForElement('.OV_stream.remote #silence-btn');
expect(await utils.isPresent('.OV_stream.remote #silence-btn')).toBeTrue();
expect(await utils.getNumberOfElements('.OV_stream.remote #silence-btn')).toEqual(1);
expect(await utils.isPresent('.OV_stream.remote #silence-btn')).to.be.true;
expect(await utils.getNumberOfElements('.OV_stream.remote #silence-btn')).equals(1);
await utils.hoverOn('.OV_stream.local');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).toEqual(0);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).equals(0);
});
it('should show the MINIMIZE button ONLY over the LOCAL video', async () => {
@ -422,7 +520,7 @@ describe('Stream UI controls and interaction features', () => {
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
expect(await utils.isPresent('#minimize-btn')).toBeTrue();
expect(await utils.isPresent('#minimize-btn')).to.be.true;
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
@ -433,11 +531,11 @@ describe('Stream UI controls and interaction features', () => {
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.remote');
expect(await utils.getNumberOfElements('#minimize-btn')).toEqual(0);
expect(await utils.getNumberOfElements('#minimize-btn')).equals(0);
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
expect(await utils.isPresent('#minimize-btn')).toBeTrue();
expect(await utils.isPresent('#minimize-btn')).to.be.true;
});
it('should minimize the LOCAL video', async () => {
@ -454,10 +552,10 @@ describe('Stream UI controls and interaction features', () => {
await browser.sleep(900);
const minimizeStream = await utils.waitForElement('.OV_stream.local');
const minimizeStreamProps: IRectangle = await minimizeStream.getRect();
expect(streamProps.height).not.toEqual(minimizeStreamProps.height);
expect(streamProps.width).not.toEqual(minimizeStreamProps.width);
expect(minimizeStreamProps.x).toBeLessThan(100);
expect(minimizeStreamProps.y).toBeLessThan(100);
expect(streamProps.height).not.equals(minimizeStreamProps.height);
expect(streamProps.width).not.equals(minimizeStreamProps.width);
expect(minimizeStreamProps.x).lessThan(100);
expect(minimizeStreamProps.y).lessThan(100);
});
it('should MAXIMIZE the local video', async () => {
@ -478,8 +576,8 @@ describe('Stream UI controls and interaction features', () => {
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(300 + marginX);
expect(streamProps.y).toEqual(300);
expect(streamProps.x).equals(300 + marginX);
expect(streamProps.y).equals(300);
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
@ -488,8 +586,8 @@ describe('Stream UI controls and interaction features', () => {
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(300 + marginX);
expect(streamProps.y).toEqual(1); //.OV_publisher element has 1px of padding
expect(streamProps.x).lessThan(300 + marginX);
expect(streamProps.y).equals(1); //.OV_publisher element has 1px of padding
});
it('should be able to dragg the minimized LOCAL video', async () => {
@ -510,8 +608,8 @@ describe('Stream UI controls and interaction features', () => {
const stream = await utils.waitForElement('.OV_stream.local');
const streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(300 + marginX);
expect(streamProps.y).toEqual(300);
expect(streamProps.x).equals(300 + marginX);
expect(streamProps.y).equals(300);
});
it('should be the MINIMIZED video ALWAYS VISIBLE when toggling panels', async () => {
@ -533,8 +631,8 @@ describe('Stream UI controls and interaction features', () => {
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(900 + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).equals(900 + marginX);
expect(streamProps.y).equals(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
@ -543,8 +641,8 @@ describe('Stream UI controls and interaction features', () => {
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeLessThan(900 + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).lessThan(900 + marginX);
expect(streamProps.y).equals(0);
// Close chat panel
await utils.clickOn('#chat-panel-btn');
@ -553,8 +651,8 @@ describe('Stream UI controls and interaction features', () => {
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeGreaterThanOrEqual(lastX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).greaterThan(lastX + marginX);
expect(streamProps.y).equals(0);
});
it('should be the MINIMIZED video go to the right when panel closes', async () => {
@ -583,8 +681,8 @@ describe('Stream UI controls and interaction features', () => {
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(newX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).equals(newX + marginX);
expect(streamProps.y).equals(0);
// Close chat panel
// There is a unstable behaviour simulating the drag and drop with selenium (the stream is not moved the first time)
@ -599,8 +697,8 @@ describe('Stream UI controls and interaction features', () => {
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeGreaterThanOrEqual(newX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).greaterThanOrEqual(newX + marginX);
expect(streamProps.y).equals(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
@ -609,8 +707,8 @@ describe('Stream UI controls and interaction features', () => {
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(lastX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).lessThan(lastX + marginX);
expect(streamProps.y).equals(0);
});
it('should be the MINIMIZED video ALWAYS VISIBLE when toggling from small to bigger panel', async () => {
@ -632,8 +730,8 @@ describe('Stream UI controls and interaction features', () => {
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(900 + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).equals(900 + marginX);
expect(streamProps.y).equals(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
@ -642,8 +740,8 @@ describe('Stream UI controls and interaction features', () => {
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeLessThan(900 + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).lessThan(900 + marginX);
expect(streamProps.y).equals(0);
// Open settings panel
await utils.togglePanel('settings');
@ -652,8 +750,8 @@ describe('Stream UI controls and interaction features', () => {
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(lastX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).lessThan(lastX + marginX);
expect(streamProps.y).equals(0);
lastX = streamProps.x;
// Open chat panel
@ -662,8 +760,8 @@ describe('Stream UI controls and interaction features', () => {
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeGreaterThanOrEqual(lastX + marginX);
expect(streamProps.y).toEqual(0);
expect(streamProps.x).greaterThan(lastX + marginX);
expect(streamProps.y).equals(0);
});
it('should show the audio detection elements when participant is speaking', async () => {
@ -676,30 +774,25 @@ describe('Stream UI controls and interaction features', () => {
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
await browser.sleep(1000);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote.speaking');
expect(await utils.getNumberOfElements('.OV_stream.remote.speaking')).toEqual(1);
// Check only one element is marked as speaker due to the local participant is muted
await utils.waitForElement('.OV_stream.speaking');
expect(await utils.getNumberOfElements('.OV_stream.speaking')).toEqual(1);
expect(await utils.getNumberOfElements('.OV_stream.remote.speaking')).to.be.equal(1);
expect(await utils.getNumberOfElements('.OV_stream.speaking')).to.be.equal(1);
});
});
describe('Video playback reliability with different media states', () => {
describe('Testing video is playing', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -709,9 +802,6 @@ describe('Video playback reliability with different media states', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
@ -740,7 +830,7 @@ describe('Video playback reliability with different media states', () => {
await browser.sleep(6000);
const exceptionQuantity = await utils.getNumberOfElements('#NO_STREAM_PLAYING_EVENT');
expect(exceptionQuantity).toEqual(0);
expect(exceptionQuantity).equals(0);
});
it('should play the participant video with only video', async () => {
@ -768,6 +858,6 @@ describe('Video playback reliability with different media states', () => {
await browser.sleep(6000);
const exceptionQuantity = await utils.getNumberOfElements('#NO_STREAM_PLAYING_EVENT');
expect(exceptionQuantity).toEqual(0);
expect(exceptionQuantity).equals(0);
});
});

View File

@ -1,18 +1,20 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
import { OPENVIDU_CALL_SERVER } from '../config';
import { WebComponentConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = TestAppConfig.appUrl;
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Toolbar button functionality for local media control', () => {
describe('Testing TOOLBAR features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress)
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
@ -22,13 +24,10 @@ describe('Toolbar button functionality for local media control', () => {
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should toggle mute/unmute on the local microphone and update the icon accordingly', async () => {
it('should mute and unmute the local microphone', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
@ -37,15 +36,15 @@ describe('Toolbar button functionality for local media control', () => {
await micButton.click();
await utils.waitForElement('#mic-btn #mic_off');
expect(await utils.isPresent('#mic-btn #mic_off')).toBeTrue();
expect(await utils.isPresent('#mic-btn #mic_off')).to.be.true;
await micButton.click();
await utils.waitForElement('#mic-btn #mic');
expect(await utils.isPresent('#mic-btn #mic')).toBeTrue();
expect(await utils.isPresent('#mic-btn #mic')).to.be.true;
});
it('should toggle mute/unmute on the local camera and update the icon accordingly', async () => {
it('should mute and unmute the local camera', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
@ -54,11 +53,11 @@ describe('Toolbar button functionality for local media control', () => {
await cameraButton.click();
await utils.waitForElement('#camera-btn #videocam_off');
expect(await utils.isPresent('#camera-btn #videocam_off')).toBeTrue();
expect(await utils.isPresent('#camera-btn #videocam_off')).to.be.true;
await cameraButton.click();
await utils.waitForElement('#camera-btn #videocam');
expect(await utils.isPresent('#camera-btn #videocam')).toBeTrue();
expect(await utils.isPresent('#camera-btn #videocam')).to.be.true;
});
});

View File

@ -0,0 +1,337 @@
import { expect } from 'chai';
import { Builder, WebDriver } from 'selenium-webdriver';
import { OPENVIDU_CALL_SERVER } from './config';
import { WebComponentConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Testing API Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
await browser.quit();
});
it('should change the captions LANG ', async () => {
await browser.get(`${url}&prejoin=false&captionsLang=es-ES`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
await utils.waitForElement('#caption-settings-btn');
await utils.clickOn('#caption-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.waitForElement('ov-captions-settings');
expect(await utils.isPresent('.captions-container')).to.be.true;
const element = await utils.waitForElement('.lang-button');
expect(await element.getText()).equal('Españolexpand_more');
});
it('should override the CAPTIONS LANG OPTIONS', async () => {
await browser.get(`${url}&prejoin=false&captionsLangOptions=true`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
await utils.waitForElement('#caption-settings-btn');
await utils.clickOn('#caption-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.waitForElement('ov-captions-settings');
expect(await utils.isPresent('.captions-container')).to.be.true;
const element = await utils.waitForElement('.lang-button');
expect(await element.getText()).equal('Espexpand_more');
await element.click();
expect(await utils.getNumberOfElements('.mat-menu-item')).equals(2);
});
});
describe('Testing panels', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
await browser.quit();
});
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')).to.be.true;
const backgroundButton = await utils.waitForElement('#background-effects-btn');
expect(await utils.isPresent('#background-effects-btn')).to.be.true;
expect(await backgroundButton.isEnabled()).to.be.true;
await backgroundButton.click();
await browser.sleep(500);
await utils.waitForElement('#background-effects-container');
expect(await utils.isPresent('#background-effects-container')).to.be.true;
element = await utils.waitForElement('#camera-button');
expect(await utils.isPresent('#camera-button')).to.be.true;
expect(await element.isEnabled()).to.be.true;
await element.click();
await browser.sleep(500);
element = await utils.waitForElement('#video-poster');
expect(await utils.isPresent('#video-poster')).to.be.true;
expect(await backgroundButton.isDisplayed()).to.be.true;
expect(await backgroundButton.isEnabled()).to.be.false;
expect(await utils.isPresent('#background-effects-container')).to.be.false;
});
});
describe('Testing captions features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
await browser.quit();
});
it('should OPEN the CAPTIONS container', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
});
it('should OPEN the SETTINGS panel from captions button', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
await utils.waitForElement('#caption-settings-btn');
await utils.clickOn('#caption-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.waitForElement('ov-captions-settings');
// Expect caption button is not present
expect(await utils.isPresent('#caption-settings-btn')).to.be.false;
});
it('should TOGGLE the CAPTIONS container from settings panel', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
await utils.waitForElement('#caption-settings-btn');
await utils.clickOn('#caption-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.waitForElement('ov-captions-settings');
expect(await utils.isPresent('.captions-container')).to.be.true;
await utils.clickOn('#captions-toggle-slide');
expect(await utils.isPresent('.captions-container')).to.be.false;
await browser.sleep(200);
await utils.clickOn('#captions-toggle-slide');
expect(await utils.isPresent('.captions-container')).to.be.true;
});
it('should change the CAPTIONS language from settings panel', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
// Checking if button panel is present
await utils.waitForElement('#more-options-menu');
expect(await utils.isPresent('#more-options-menu')).to.be.true;
// Checking if captions button is present
await utils.waitForElement('#captions-btn');
expect(await utils.isPresent('#captions-btn')).to.be.true;
await utils.clickOn('#captions-btn');
await utils.waitForElement('.captions-container');
await utils.waitForElement('#caption-settings-btn');
await utils.clickOn('#caption-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).to.be.true;
await utils.waitForElement('ov-captions-settings');
expect(await utils.isPresent('.captions-container')).to.be.true;
await utils.clickOn('.lang-button');
await browser.sleep(500);
await utils.clickOn('#es-ES');
await utils.clickOn('.panel-close-button');
const button = await utils.waitForElement('#caption-settings-btn');
expect(await button.getText()).equals('settingsEspañol');
});
});

View File

@ -0,0 +1,115 @@
import { expect } from 'chai';
import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { OPENVIDU_CALL_SERVER } from './config';
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
describe('Testing PRO features with OpenVidu CE', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(WebComponentConfig.browserName)
.withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(WebComponentConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
await browser.quit();
});
// TODO: Uncomment when background feature is supported
// it('should SHOW the VIRTUAL BACKGROUND PRO feature dialog', async () => {
// await browser.get(`${url}&prejoin=true`);
// await utils.checkPrejoinIsPresent();
// await utils.waitForElement('#background-effects-btn');
// await utils.clickOn('#background-effects-btn');
// await utils.chceckProFeatureAlertIsPresent();
// // Close alert
// await (await utils.waitForElement('html')).sendKeys(Key.ESCAPE);
// // Join to room
// await utils.clickOn('#join-button');
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// await utils.waitForElement('#virtual-bg-btn');
// await utils.clickOn('#virtual-bg-btn');
// // Expect it shows the pro feature alert
// await utils.chceckProFeatureAlertIsPresent();
// });
// it('should SHOW the CAPTIONS PRO feature dialog', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// await utils.waitForElement('#toolbar-settings-btn');
// expect(await utils.isPresent('#toolbar-settings-btn')).to.be.true;
// await utils.clickOn('#toolbar-settings-btn');
// // Expect captions panel shows the pro feature content
// await utils.waitForElement('#settings-container');
// await utils.clickOn('#captions-opt');
// await browser.sleep(1000);
// await utils.waitForElement('.pro-feature');
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).to.be.true;
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).to.be.true;
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('ov-pro-feature-template');
// expect(await utils.isPresent('.captions-container')).to.be.false;
// });
});

View File

@ -6,7 +6,7 @@ if [[ -z "$BASEHREF_VERSION" ]]; then
fi
# Replace version from "stable" to the specified one in all TypeDoc links
grep -rl '/latest/' projects src | xargs sed -i -e 's|/latest/|/'${BASEHREF_VERSION}'/|g'
grep -rl '/en/stable/' projects src | xargs sed -i -e 's|/en/stable/|/en/'${BASEHREF_VERSION}'/|g'
# Replace testapp README by openvidu-components-angular README
mv README.md README-testapp.md
@ -16,12 +16,12 @@ cp ./projects/openvidu-components-angular/README.md .
npm run doc:build
# Return links to "stable" version
grep -rl '/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/'${BASEHREF_VERSION}'/|/latest/|g'
grep -rl '/en/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/en/'${BASEHREF_VERSION}'/|/en/stable/|g'
# Undo changes with READMEs
rm README.md
mv README-testapp.md README.md
# Clean previous docs from openvidu.io repo and copy new ones
# Clean previous docs from openvidu.io-docs repo and copy new ones
npm run doc:clean-copy

View File

@ -204,6 +204,7 @@ npm install openvidu-components-angular@3.0.0
- `publishScreen` and `unpublishScreen` methods have been renamed to `setScreenShareEnabled`
- `isSttReadyObs` observable has been deleted
- `connectRoom` method has been added
- `getRoomMetadata` method has been added to get the metadata of the room
- `getRoomName` method has been added to get the name of the room
- ##### Panel Service (`PanelService`):

View File

@ -0,0 +1,60 @@
const fs = require('fs-extra');
const concat = require('concat');
const VERSION = require('./package.json').version;
const ovWebcomponentRCPath = './dist/openvidu-webcomponent-rc';
const ovWebcomponentProdPath = './dist/openvidu-webcomponent';
module.exports.buildWebcomponent = async () => {
console.log('Building OpenVidu Web Component (' + VERSION + ')');
const tutorialWcPath = '../../openvidu-tutorials/openvidu-webcomponent/web';
const e2eWcPath = './e2e/webcomponent-app';
try {
await buildElement();
await copyFiles(tutorialWcPath);
await copyFiles(e2eWcPath);
await renameWebComponentTestName(e2eWcPath);
console.log(`OpenVidu Web Component (${VERSION}) built`);
} catch (error) {
console.error(error);
}
};
async function buildElement() {
const files = [`${ovWebcomponentRCPath}/runtime.js`, `${ovWebcomponentRCPath}/main.js`, `${ovWebcomponentRCPath}/polyfills.js`];
try {
await fs.ensureDir('./dist/openvidu-webcomponent');
await concat(files, `${ovWebcomponentProdPath}/openvidu-webcomponent-${VERSION}.js`);
await fs.copy(`${ovWebcomponentRCPath}/styles.css`, `${ovWebcomponentProdPath}/openvidu-webcomponent-${VERSION}.css`);
// await fs.copy(
// "./dist/openvidu-webcomponent/assets",
// "./openvidu-webcomponent/assets"
// );
} catch (err) {
console.error('Error executing build function in webcomponent-builds.js');
throw err;
}
}
function renameWebComponentTestName(dir) {
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.js`, `${dir}/openvidu-webcomponent-dev.js`);
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.css`, `${dir}/openvidu-webcomponent-dev.css`);
}
async function copyFiles(destination) {
if (fs.existsSync(destination)) {
try {
console.log(`Copying openvidu-webcomponent files from: ${ovWebcomponentProdPath} to: ${destination}`);
await fs.ensureDir(ovWebcomponentProdPath);
await fs.copy(ovWebcomponentProdPath, destination);
} catch (err) {
console.error('Error executing copy function in webcomponent-builds.js');
throw err;
}
}
}
this.buildWebcomponent();

File diff suppressed because it is too large Load Diff

View File

@ -1,60 +1,65 @@
{
"dependencies": {
"@angular/animations": "19.2.8",
"@angular/cdk": "19.2.11",
"@angular/common": "19.2.8",
"@angular/core": "19.2.8",
"@angular/forms": "19.2.8",
"@angular/material": "19.2.11",
"@angular/platform-browser": "19.2.8",
"@angular/platform-browser-dynamic": "19.2.8",
"@angular/router": "19.2.8",
"@livekit/track-processors": "^0.5.6",
"@types/dom-mediacapture-transform": "^0.1.11",
"@angular/animations": "18.0.0",
"@angular/cdk": "18.0.0",
"@angular/common": "18.0.0",
"@angular/core": "18.0.0",
"@angular/forms": "18.0.0",
"@angular/material": "18.0.0",
"@angular/platform-browser": "18.0.0",
"@angular/platform-browser-dynamic": "18.0.0",
"@angular/router": "18.0.0",
"@livekit/track-processors": "0.3.2",
"autolinker": "4.0.0",
"livekit-client": "2.11.4",
"rxjs": "7.8.1",
"tslib": "2.7.0",
"zone.js": "^0.15.0"
"livekit-client": "2.1.0",
"rxjs": "7.5.7",
"tslib": "2.3.1",
"zone.js": "^0.14.6"
},
"devDependencies": {
"@angular-devkit/build-angular": "19.2.9",
"@angular/cli": "19.2.9",
"@angular/compiler": "19.2.8",
"@angular/compiler-cli": "19.2.8",
"@angular-devkit/build-angular": "18.0.1",
"@angular/cli": "18.0.1",
"@angular/compiler": "18.0.0",
"@angular/compiler-cli": "18.0.0",
"@angular/elements": "18.0.0",
"@compodoc/compodoc": "^1.1.25",
"@types/jasmine": "^5.1.4",
"@types/node": "20.12.14",
"@types/chai": "4.3.6",
"@types/dom-mediacapture-transform": "0.1.9",
"@types/dom-webcodecs": "0.1.11",
"@types/mocha": "9.1.0",
"@types/node": "20.12.7",
"@types/selenium-webdriver": "4.1.16",
"@types/ws": "^8.5.12",
"chromedriver": "138.0.0",
"@types/ws": "^8.5.4",
"chai": "4.3.6",
"chromedriver": "116.0.0",
"codelyzer": "6.0.2",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"http-server": "14.1.1",
"husky": "^9.1.6",
"jasmine": "^5.3.1",
"jasmine-core": "5.3.0",
"husky": "^8.0.3",
"jasmine-core": "3.10.1",
"jasmine-spec-reporter": "7.0.0",
"karma": "^6.4.4",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "^2.2.1",
"karma": "^6.3.9",
"karma-chrome-launcher": "3.1.1",
"karma-coverage": "^2.0.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.7.0",
"karma-junit-reporter": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-notify-reporter": "1.3.0",
"lint-staged": "^15.2.10",
"ng-packagr": "19.2.2",
"npm-watch": "^0.13.0",
"prettier": "3.3.3",
"selenium-webdriver": "4.32.0",
"ts-node": "10.9.2",
"lint-staged": "^14.0.1",
"mocha": "9.2.2",
"ng-packagr": "18.0.0",
"npm-watch": "^0.11.0",
"prettier": "3.0.3",
"selenium-webdriver": "4.12.0",
"ts-node": "10.4.0",
"tslint": "6.1.3",
"typescript": "5.8.3",
"webpack-bundle-analyzer": "^4.10.2"
"typescript": "5.4.5",
"webpack-bundle-analyzer": "^4.5.0"
},
"name": "openvidu-components-testapp",
"private": true,
@ -69,36 +74,31 @@
}
},
"scripts": {
"start": "ng serve --configuration development --open",
"start-prod": "npx http-server ./dist/openvidu-components-testapp/browser --port 4200",
"start:ssl": "ng serve --ssl --configuration development --host 0.0.0.0 --port 5080",
"husky": "cd .. && husky install",
"build": "ng build openvidu-components-testapp --configuration production",
"bundle-report": "ng build openvidu-webcomponent --stats-json --configuration production && webpack-bundle-analyzer dist/openvidu-webcomponent/stats.json",
"doc:build": "npx compodoc -c ./projects/openvidu-components-angular/doc/.compodocrc.json",
"doc:generate-directives-tutorials": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tutorials.js",
"doc:generate-directive-tables": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tables.js",
"doc:clean-copy": "rm -rf ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular && cp -r ./docs/openvidu-components-angular/ ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular",
"doc:clean-copy": "rm -rf ../../openvidu-docs/docs/docs/reference-docs/openvidu-components-angular && cp -r ./docs/openvidu-components-angular/ ../../openvidu-docs/docs/docs/reference-docs/openvidu-components-angular",
"doc:serve": "npx compodoc -c ../openvidu-components-angular/projects/openvidu-components-angular/doc/.compodocrc.json --serve --port 7000",
"doc:serve-watch": "npm-watch doc:serve",
"lib:serve": "ng build openvidu-components-angular --watch",
"lib:build": "ng build openvidu-components-angular --configuration production && cd ./dist/openvidu-components-angular",
"lib:pack": "cd ./dist/openvidu-components-angular && npm pack",
"lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call/frontend",
"lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call-livekit/openvidu-call-front",
"lib:e2e": "tsc --project ./e2e && npx mocha --recursive --timeout 30000 ./e2e/dist/angular.test.js",
"lib:e2e-ci": "cross-env LAUNCH_MODE=CI npm run lib:e2e",
"lib:serve": "ng build openvidu-components-angular --watch",
"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-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: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-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",
"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"
"lint": "ng lint",
"start": "ng serve --configuration development --open",
"start-prod": "npx http-server ./dist/openvidu-components-testapp/ --port 4200",
"start:ssl": "ng serve --ssl --configuration development --host 0.0.0.0 --port 5080",
"webcomponent:testing-build": "./node_modules/@angular/cli/bin/ng.js build openvidu-webcomponent --configuration testing && node ./openvidu-webcomponent-build.js",
"webcomponent:build": "./node_modules/@angular/cli/bin/ng.js build openvidu-webcomponent --configuration production && node ./openvidu-webcomponent-build.js",
"webcomponent:e2e": "tsc --project ./e2e && npx mocha --recursive --timeout 30000 ./e2e/dist/webcomponent-e2e/**/*.test.js",
"webcomponent:e2e-ci": "cross-env LAUNCH_MODE=CI npm run webcomponent:e2e",
"webcomponent:serve-testapp": "npx http-server ./e2e/webcomponent-app/ && echo http://localhost:8080/?OV_URL=https://localhost:4443&OV_SECRET=MY_SECRET&prejoin=false",
"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"
},
"version": "3.3.0"
"version": "3.0.0-beta2"
}

View File

@ -8,7 +8,7 @@ Angular Components are the simplest way to create real-time videoconferencing ap
## Getting Started
To get started with OpenVidu Components Angular, visit our [**Getting Started guide**](https://openvidu.io/latest/docs/ui-components/angular-components/).
To get started with OpenVidu Components Angular, visit our [**Getting Started guide**](https://openvidu.io/docs/ui-components/angular-components/).
1. Create an Angular Project (>= 17.0.0)
@ -54,28 +54,24 @@ You can also customize the styles in your `styles.scss` file:
```scss
:root {
/* Basic colors */
--ov-background-color: #303030; // Background color
--ov-surface-color: #ffffff; // Surfaces colors (panels, dialogs)
--ov-primary-color: #303030;
--ov-secondary-color: #3e3f3f;
--ov-tertiary-color: #598eff;
--ov-warn-color: #eb5144;
--ov-accent-color: #ffae35;
--ov-light-color: #e6e6e6;
/* Text colors */
--ov-text-primary-color: #ffffff; // Text color over primary background
--ov-text-surface-color: #1d1d1d; // Text color over surface background
--ov-logo-background-color: #3a3d3d;
/* Action colors */
--ov-primary-action-color: #273235; // Primary color for buttons, etc.
--ov-secondary-action-color: #f1f1f1; // Secondary color for buttons, etc.
--ov-accent-action-color: #0089ab; // Color for highlighted elements
--ov-text-color: #ffffff;
/* Status colors */
--ov-error-color: #eb5144; // Error color
--ov-warn-color: #ffba53; // Warning color
--ov-panel-text-color: #1d1d1d;
--ov-panel-background: #ffffff;
/* Radius */
--ov-toolbar-buttons-radius: 50%; // Radius for toolbar buttons
--ov-leave-button-radius: 10px; // Radius for leave button
--ov-video-radius: 5px; // Radius for video elements
--ov-surface-radius: 5px; // Radius for surface elements
--ov-buttons-radius: 50%;
--ov-leave-button-radius: 10px;
--ov-video-radius: 5px;
--ov-panel-radius: 5px;
}
```
@ -92,7 +88,7 @@ You can also customize the styles in your `styles.scss` file:
## API Documentation
For detailed information on OpenVidu Angular Components, refer to our [**API Reference**](https://openvidu.io/latest/docs/reference-docs/openvidu-components-angular/).
For detailed information on OpenVidu Angular Components, refer to our [**API Reference**](https://openvidu.io/docs/reference-docs/openvidu-components-angular).
## Support

View File

@ -7,33 +7,24 @@ const apiDirectivesTable =
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
/**
* Get all directive files from the API directives directory
*/
function getDirectiveFiles() {
// Directory where directive files are located
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
return listFiles(directivesDir, '.directive.ts');
}
/**
* Get all component files
*/
function getComponentFiles() {
// Directory where component files are located
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
return listFiles(componentsDir, '.component.ts');
}
/**
* Get all admin files
*/
function getAdminFiles() {
// Directory where component files are located
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
return listFiles(componentsDir, '.component.ts');
}
/**
* List all files with specific extension in directory
*/
function listFiles(directoryPath, fileExtension) {
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
if (files.length === 0) {
@ -42,265 +33,128 @@ function listFiles(directoryPath, fileExtension) {
return files;
}
/**
* Extract component selector from component file
*/
function getComponentSelector(componentFile) {
const componentContent = fs.readFileSync(componentFile, 'utf8');
const selectorMatch = componentContent.match(/@Component\({[^]*?selector:\s*['"]([^'"]+)['"][^]*?}\)/s);
if (!selectorMatch) {
throw new Error(`Unable to find selector in component file: ${componentFile}`);
}
return selectorMatch[1];
}
/**
* Check if a directive class has @internal annotation
*/
function isInternalDirective(directiveContent, className) {
const classRegex = new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)?\\s*@Directive\\([\\s\\S]*?\\)\\s*export\\s+class\\s+${escapeRegex(className)}`, 'g');
const match = classRegex.exec(directiveContent);
if (match && match[1]) {
return match[1].includes('@internal');
}
return false;
}
/**
* Extract attribute name from selector for a specific component
*/
function extractAttributeForComponent(selector, componentSelector) {
// Split selector by comma and trim whitespace
const selectorParts = selector.split(',').map(part => part.trim());
// Find the part that matches our component
for (const part of selectorParts) {
if (part.includes(componentSelector)) {
// Extract attribute from this specific part
const attributeMatch = part.match(/\[([^\]]+)\]/);
if (attributeMatch) {
return attributeMatch[1];
}
}
}
// Fallback: if no specific match, return the first attribute found
const fallbackMatch = selector.match(/\[([^\]]+)\]/);
return fallbackMatch ? fallbackMatch[1] : null;
}
/**
* Extract all directive classes from a directive file
*/
function extractDirectiveClasses(directiveContent) {
const classes = [];
// Regex to find all directive class definitions with their preceding @Directive decorators
const directiveClassRegex = /@Directive\(\s*{\s*selector:\s*['"]([^'"]+)['"][^}]*}\s*\)\s*export\s+class\s+(\w+)/gs;
let match;
while ((match = directiveClassRegex.exec(directiveContent)) !== null) {
const selector = match[1];
const className = match[2];
// Skip internal directives
if (isInternalDirective(directiveContent, className)) {
console.log(`Skipping internal directive: ${className}`);
continue;
}
classes.push({
selector,
className
});
}
return classes;
}
/**
* Extract all directives from a directive file that match a component selector
*/
function extractDirectivesForComponent(directiveFile, componentSelector) {
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
const directives = [];
// Get all directive classes in the file (excluding internal ones)
const directiveClasses = extractDirectiveClasses(directiveContent);
// Filter classes that match the component selector
const matchingClasses = directiveClasses.filter(directiveClass =>
directiveClass.selector.includes(componentSelector)
);
// For each matching class, extract input type information
matchingClasses.forEach(directiveClass => {
// Extract the correct attribute name for this component
const attributeName = extractAttributeForComponent(directiveClass.selector, componentSelector);
if (attributeName) {
const inputInfo = extractInputInfo(directiveContent, attributeName, directiveClass.className);
if (inputInfo) {
directives.push({
attribute: attributeName,
type: inputInfo.type,
className: directiveClass.className
});
}
}
});
return directives;
}
/**
* Extract input information (type) for a specific attribute and class
*/
function extractInputInfo(directiveContent, attributeName, className) {
// Create a regex to find the specific class section
const classRegex = new RegExp(`export\\s+class\\s+${escapeRegex(className)}[^}]*?{([^]*?)(?=export\\s+class|$)`, 's');
const classMatch = directiveContent.match(classRegex);
if (!classMatch) {
console.warn(`Could not find class ${className}`);
return null;
}
const classContent = classMatch[1];
// Regex to find the @Input setter for this attribute within the class
const inputRegex = new RegExp(
`@Input\\(\\)\\s+set\\s+${escapeRegex(attributeName)}\\s*\\(\\s*\\w+:\\s*([^)]+)\\s*\\)`,
'g'
);
const inputMatch = inputRegex.exec(classContent);
if (!inputMatch) {
console.warn(`Could not find @Input setter for attribute: ${attributeName} in class: ${className}`);
return null;
}
let type = inputMatch[1].trim();
// Clean up the type (remove extra whitespace, etc.)
type = type.replace(/\s+/g, ' ');
return {
type: type
};
}
/**
* Escape special regex characters
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Generate API directives table for components
*/
function generateApiDirectivesTable(componentFiles, directiveFiles) {
componentFiles.forEach((componentFile) => {
try {
console.log(`Processing component: ${componentFile}`);
const componentSelector = getComponentSelector(componentFile);
const readmeFilePath = componentFile.replace('.ts', '.md');
console.log(`Component selector: ${componentSelector}`);
// Initialize table with header
initializeDynamicTableContent(readmeFilePath);
const allDirectives = [];
// Extract directives from all directive files
directiveFiles.forEach((directiveFile) => {
console.log(`Checking directive file: ${directiveFile}`);
const directives = extractDirectivesForComponent(directiveFile, componentSelector);
allDirectives.push(...directives);
});
console.log(`Found ${allDirectives.length} directives for ${componentSelector}`);
// Sort directives alphabetically by attribute name
allDirectives.sort((a, b) => a.attribute.localeCompare(b.attribute));
// Add rows to table
allDirectives.forEach((directive) => {
addRowToTable(readmeFilePath, directive.attribute, directive.type, directive.className);
});
// If no directives found, add "no directives" message
if (allDirectives.length === 0) {
removeApiTableContent(readmeFilePath);
}
} catch (error) {
console.error(`Error processing component ${componentFile}:`, error.message);
}
});
}
/**
* Initialize table with header
*/
function initializeDynamicTableContent(filePath) {
replaceDynamicTableContent(filePath, apiDirectivesTable);
}
/**
* Replace table content with "no directives" message
*/
function removeApiTableContent(filePath) {
const content = '_No API directives available for this component_. \n';
replaceDynamicTableContent(filePath, content);
}
/**
* Add a row to the markdown table
*/
function addRowToTable(filePath, parameter, type, reference) {
function apiTableContentIsEmpty(filePath) {
try {
const data = fs.readFileSync(filePath, 'utf8');
const startIdx = data.indexOf(startApiLine);
const endIdx = data.indexOf(endApiLine);
if (startIdx !== -1 && endIdx !== -1) {
const capturedContent = data.substring(startIdx + startApiLine.length, endIdx).trim();
return capturedContent === apiDirectivesTable;
}
return false;
} catch (error) {
return false;
}
}
function writeApiDirectivesTable(componentFiles, directiveFiles) {
componentFiles.forEach((componentFile) => {
// const componentName = componentFile.split('/').pop()
const componentFileName = componentFile.split('/').pop().replace('.component.ts', '');
const componentName = componentFileName.replace(/(?:^|-)([a-z])/g, (_, char) => char.toUpperCase());
const readmeFilePath = componentFile.replace('.ts', '.md');
const componentContent = fs.readFileSync(componentFile, 'utf8');
const selectorMatch = componentContent.match(/@Component\({[^]*?selector: ['"]([^'"]+)['"][^]*?}\)/);
const componentSelectorName = selectorMatch[1];
initializeDynamicTableContent(readmeFilePath);
if (!componentSelectorName) {
throw new Error(`Unable to find the component name in the file ${componentFileName}`);
}
// const directiveRegex = new RegExp(`@Directive\\(\\s*{[^}]*selector:\\s*['"]${componentName}\\s*\\[([^'"]+)\\]`, 'g');
const directiveRegex = /^\s*(selector):\s*(['"])(.*?)\2\s*$/gm;
directiveFiles.forEach((directiveFile) => {
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
let directiveNameMatch;
while ((directiveNameMatch = directiveRegex.exec(directiveContent)) !== null) {
if (directiveNameMatch[0].includes('@Directive({\n//')) {
// Skip directives that are commented out
continue;
}
const selectorValue = directiveNameMatch[3].split(',');
const directiveMatch = selectorValue.find((value) => value.includes(componentSelectorName));
if (directiveMatch) {
const directiveName = directiveMatch.match(/\[(.*?)\]/).pop();
const className = directiveName.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase()) + 'Directive';
const inputRegex = new RegExp(
`@Input\\(\\)\\s+set\\s+(${directiveName.replace(/\[/g, '\\[').replace(/\]/g, '\\]')})\\((\\w+):\\s+(\\w+)`
);
const inputMatch = directiveContent.match(inputRegex);
const inputType = inputMatch && inputMatch.pop();
if (inputType && className) {
let finalClassName = componentName === 'Videoconference' ? className : componentName + className;
addRowToTable(readmeFilePath, directiveName, inputType, finalClassName);
}
} else {
console.log(`The selector "${componentSelectorName}" does not match with ${selectorValue}. Skipping...`);
}
}
});
if (apiTableContentIsEmpty(readmeFilePath)) {
removeApiTableContent(readmeFilePath);
}
});
}
// Function to add a row to a Markdown table in a file
function addRowToTable(filePath, parameter, type, reference) {
// Read the current content of the file
try {
const data = fs.readFileSync(filePath, 'utf8');
// Define the target line and the Markdown row
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
// Find the line that contains the table
const lines = data.split('\n');
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
if (targetIndex !== -1) {
// Insert the new row above the target line
lines.splice(targetIndex, 0, markdownRow);
// Join the lines back together
const updatedContent = lines.join('\n');
// Write the updated content to the file
fs.writeFileSync(filePath, updatedContent, 'utf8');
console.log(`Added directive: ${parameter} -> ${reference}`);
console.log('Row added successfully.');
} else {
console.error('End marker not found in file:', filePath);
console.error('Table not found in the file.');
}
} catch (error) {
console.error('Error adding row to table:', error);
console.error('Error writing to file:', error);
}
}
/**
* Replace content between start and end markers
*/
function replaceDynamicTableContent(filePath, content) {
// Read the current content of the file
try {
const data = fs.readFileSync(filePath, 'utf8');
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
// Replace the content between startLine and endLine with the replacement table
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
return startApiLine + '\n' + content + '\n' + endApiLine;
});
// Write the modified content back to the file
fs.writeFileSync(filePath, modifiedContent, 'utf8');
console.log(`Updated table content in: ${filePath}`);
} catch (error) {
if (error.code === 'ENOENT') {
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
@ -310,27 +164,7 @@ function replaceDynamicTableContent(filePath, content) {
}
}
// Main execution
if (require.main === module) {
try {
const directiveFiles = getDirectiveFiles();
const componentFiles = getComponentFiles();
const adminFiles = getAdminFiles();
console.log('Starting directive table generation...');
generateApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
console.log('Directive table generation completed!');
} catch (error) {
console.error('Script execution failed:', error);
process.exit(1);
}
}
// Export functions for testing
module.exports = {
generateApiDirectivesTable,
getDirectiveFiles,
getComponentFiles,
getAdminFiles
};
writeApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);

View File

@ -5,7 +5,8 @@
"../src/lib/directives/**/*.ts",
"../src/lib/services/**/*.ts",
"../src/lib/models/**/*.ts",
"../src/lib/pipes/**/*.ts"
"../src/lib/pipes/**/*.ts",
// "../../../src/app/openvidu-webcomponent/**/*.ts",
],
"exclude": [
"src/test.ts",

View File

@ -38,7 +38,7 @@ module.exports = function (config) {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});

View File

@ -1,418 +0,0 @@
{
"name": "openvidu-components-angular",
"version": "3.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvidu-components-angular",
"version": "3.3.0",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^17.0.0 || ^18.0.0",
"@angular/cdk": "^17.0.0 || ^18.0.0",
"@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0",
"@angular/material": "^17.0.0 || ^18.0.0",
"@livekit/track-processors": "^0.3.2",
"autolinker": "^4.0.0",
"buffer": "^6.0.3",
"livekit-client": "^2.1.0"
}
},
"node_modules/@angular/animations": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.8.tgz",
"integrity": "sha512-dMSn2hg70siv3lhP+vqhMbgc923xw6XBUvnpCPEzhZqFHvPXfh/LubmsD5RtqHmjWebXtgVcgS+zg3Gq3jB2lg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.2.8"
}
},
"node_modules/@angular/cdk": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.8.tgz",
"integrity": "sha512-J8A2FkwTBzLleAEWz6EgW73dEoeq87GREBPjTv8+2JV09LX+V3hnbgNk6zWq5k4OXtQNg9WrWP9QyRbUyA597g==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^7.1.2"
},
"peerDependencies": {
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/common": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.8.tgz",
"integrity": "sha512-TYsKtE5nVaIScWSLGSO34Skc+s3hB/BujSddnfQHoNFvPT/WR0dfmdlpVCTeLj+f50htFoMhW11tW99PbK+whQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.2.8",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/core": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.8.tgz",
"integrity": "sha512-NwIuX/Iby1jT6Iv1/s6S3wOFf8xfuQR3MPGvKhGgNtjXLbHG+TXceK9+QPZC0s9/Z8JR/hz+li34B79GrIKgUg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"rxjs": "^6.5.3 || ^7.4.0",
"zone.js": "~0.14.10"
}
},
"node_modules/@angular/forms": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.8.tgz",
"integrity": "sha512-JCLki7KC6D5vF6dE6yGlBmW33khIgpHs8N9SzuiJtkQqNDTIQA8cPsGV6qpLpxflxASynQOX5lDkWYdQyfm77Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/common": "18.2.8",
"@angular/core": "18.2.8",
"@angular/platform-browser": "18.2.8",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.8.tgz",
"integrity": "sha512-wQGMVsfQ9lQfih2VsWAvV4z3S3uBxrxc61owlE+K0T1BxH9u/jo3A/rnRitIdvR/L4NnYlfhCnmrW9K+Pl+WCg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^18.0.0 || ^19.0.0",
"@angular/cdk": "18.2.8",
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"@angular/forms": "^18.0.0 || ^19.0.0",
"@angular/platform-browser": "^18.0.0 || ^19.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.8.tgz",
"integrity": "sha512-EPai4ZPqSq3ilLJUC85kPi9wo5j5suQovwtgRyjM/75D9Qy4TV19g8hkVM5Co/zrltO8a2G6vDscCNI5BeGw2A==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/animations": "18.2.8",
"@angular/common": "18.2.8",
"@angular/core": "18.2.8"
},
"peerDependenciesMeta": {
"@angular/animations": {
"optional": true
}
}
},
"node_modules/@bufbuild/protobuf": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
"license": "(Apache-2.0 AND BSD-3-Clause)",
"peer": true
},
"node_modules/@livekit/protocol": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.24.0.tgz",
"integrity": "sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
}
},
"node_modules/@livekit/track-processors": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.3.2.tgz",
"integrity": "sha512-4JUCzb7yIKoVsTo8J6FTzLZJHcI6DihfX/pGRDg0SOGaxprcDPrt8jaDBBTsnGBSXHeMxl2ugN+xQjdCWzLKEA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@mediapipe/holistic": "0.5.1675471629",
"@mediapipe/tasks-vision": "0.10.9"
},
"peerDependencies": {
"livekit-client": "^1.12.0 || ^2.1.0"
}
},
"node_modules/@mediapipe/holistic": {
"version": "0.5.1675471629",
"resolved": "https://registry.npmjs.org/@mediapipe/holistic/-/holistic-0.5.1675471629.tgz",
"integrity": "sha512-qY+cxtDeSOvVtevrLgnodiwXYaAtPi7dHZtNv/bUCGEjFicAOYtMmrZSqMmbPkTB2+4jLnPF1vgshkAqQRSYAw==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.9",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.9.tgz",
"integrity": "sha512-/gFguyJm1ng4Qr7VVH2vKO+zZcQd8wc3YafUfvBuYFX0Y5+CvrV+VNPEVkl5W/gUZF5KNKNZAiaHPULGPCIjyQ==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/autolinker": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/autolinker/-/autolinker-4.0.0.tgz",
"integrity": "sha512-fl5Kh6BmEEZx+IWBfEirnRUU5+cOiV0OK7PEt0RBKvJMJ8GaRseIOeDU3FKf4j3CE5HVefcjHmhYPOcaVt0bZw==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/livekit-client": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.5.9.tgz",
"integrity": "sha512-oDpK6SKYB1F+mNO+25DA0bF0cD2XoOJeD8ji4YQpzDBQv2IxeyKrQhoqXAqrYgIKuiMNkImSf+yg2v7EHSl4Og==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@livekit/protocol": "1.24.0",
"events": "^3.3.0",
"loglevel": "^1.8.0",
"sdp-transform": "^2.14.1",
"ts-debounce": "^4.0.0",
"tslib": "2.7.0",
"typed-emitter": "^2.1.0",
"webrtc-adapter": "^9.0.0"
}
},
"node_modules/loglevel": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/parse5": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz",
"integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"entities": "^4.5.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
"license": "MIT",
"peer": true
},
"node_modules/sdp-transform": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
"license": "MIT",
"peer": true,
"bin": {
"sdp-verify": "checker.js"
}
},
"node_modules/ts-debounce": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz",
"integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==",
"license": "MIT",
"peer": true
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"license": "MIT",
"peer": true,
"optionalDependencies": {
"rxjs": "*"
}
},
"node_modules/webrtc-adapter": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz",
"integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/zone.js": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
"license": "MIT",
"peer": true
}
}
}

View File

@ -4,16 +4,15 @@
},
"name": "openvidu-components-angular",
"peerDependencies": {
"@angular/animations": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/cdk": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/material": "^17.0.0 || ^18.0.0 || ^19.0.0",
"@angular/animations": "^17.0.0 || ^18.0.0",
"@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0",
"@angular/material": "^17.0.0 || ^18.0.0",
"autolinker": "^4.0.0",
"buffer": "^6.0.3",
"livekit-client": "^2.1.0",
"@livekit/track-processors": "^0.3.2"
"livekit-client": "2.1.0",
"@livekit/track-processors": "0.3.2"
},
"version": "3.3.0"
"version": "3.0.0-beta2"
}

View File

@ -1,6 +1,6 @@
<div class="dashboard-container">
<mat-toolbar class="header">
<span>{{ title || ('ADMIN.DASHBOARD_TITLE' | translate) }}</span>
<span>{{ 'ADMIN.DASHBOARD_TITLE' | translate }}</span>
<span class="toolbar-spacer"></span>
<button class="logout-btn" mat-icon-button aria-label="Refresh" (click)="logout()">

View File

@ -4,6 +4,5 @@ With the following directives you can modify the default User Interface with the
<!-- start-dynamic-api-directives-content -->
| **Parameter** | **Type** | **Reference** |
|:--------------------------------: | :-------: | :---------------------------------------------: |
| **navbarTitle** | `string` | [AdminDashboardTitleDirective](../directives/AdminDashboardTitleDirective.html) |
| **recordingsList** | `RecordingInfo[]` | [AdminDashboardRecordingsListDirective](../directives/AdminDashboardRecordingsListDirective.html) |
| **recordingsList** | `RecordingInfo` | [AdminDashboardRecordingsListDirective](../directives/AdminDashboardRecordingsListDirective.html) |
<!-- end-dynamic-api-directives-content -->

View File

@ -4,16 +4,16 @@
.header {
height: 50px;
background-color: var(--ov-secondary-action-color);
color: var(--ov-text-primary-color);
background-color: var(--ov-secondary-color);
color: var(--ov-text-color);
}
.logout-btn {
color: var(--ov-text-primary-color);
color: var(--ov-text-color);
}
.dashboard-body {
background-color: var(--ov-secondary-action-color);
background-color: var(--ov-secondary-color);
height: calc(100% - 75px);
}
.toolbar-spacer {
@ -41,17 +41,17 @@
#sort-menu-btn {
margin-left: 5px;
background-color: var(--ov-surface-color);
color: var(--ov-text-primary-color);
background-color: var(--ov-panel-background);
color: var(--ov-panel-text-color);
}
.search-bar {
height: 95%;
width: 30%;
display: flex;
background-color: var(--ov-surface-color);
background-color: var(--ov-panel-background);
padding: 0px 10px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
}
#search-input {
@ -87,7 +87,7 @@
}
.recordings-container {
background-color: var(--ov-secondary-action-color);
background-color: var(--ov-secondary-color);
height: calc(100% - 50px);
overflow-y: auto;
overflow-x: hidden;
@ -106,7 +106,7 @@
display: flex;
justify-content: center;
align-items: center;
background-color: var(--ov-surface-color);
background-color: var(--ov-panel-background);
}
}
@ -135,18 +135,18 @@
margin-right: -50%;
top: 50%;
left: 50%;
background-color: var(--ov-secondary-action-color);
border-radius: var(--ov-surface-radius);
background-color: var(--ov-logo-background-color);
border-radius: var(--ov-panel-radius);
}
.video-btns button #play {
color: var(--ov-text-primary-color);
color: var(--ov-text-color);
}
.video-btns button #download {
color: var(--ov-accent-action-color);
color: var(--ov-tertiary-color);
}
.video-btns button #delete {
color: var(--ov-error-color);
color: var(--ov-warn-color);
}
.video-info-container > div {
@ -167,7 +167,7 @@
.video-card-tag {
font-size: 13px;
color: var(--ov-text-primary-color);
color: var(--ov-panel-text-color);
font-weight: bold;
}
@ -204,16 +204,16 @@
transform: translateX(-50%);
.load-more-btn {
background-color: var(--ov-surface-color);
color: var(--ov-text-primary-color);
border-radius: var(--ov-surface-radius);
background-color: var(--ov-panel-background);
color: var(--ov-panel-text-color);
border-radius: var(--ov-panel-radius);
}
}
.footer {
height: 25px;
background-color: var(--ov-background-color);
color: var(--ov-text-primary-color);
background-color: var(--ov-primary-color);
color: var(--ov-text-color);
position: absolute;
bottom: 0;
left: 0;
@ -225,7 +225,7 @@
gap: 2px;
}
.footer a {
color: var(--ov-accent-action-color);
color: var(--ov-tertiary-color);
padding-left: 5px;
}
@ -234,7 +234,7 @@
width: 100%;
display: table;
text-align: center;
color: var(--ov-text-primary-color);
color: var(--ov-text-color);
}
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex {

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminDashboardComponent } from './admin-dashboard.component';
describe('DashboardComponent', () => {
let component: AdminDashboardComponent;
let fixture: ComponentFixture<AdminDashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AdminDashboardComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AdminDashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -2,14 +2,13 @@ import { Component, OnInit, Output, EventEmitter, OnDestroy } from '@angular/cor
import { Subscription } from 'rxjs';
import { RecordingDeleteRequestedEvent, RecordingInfo, RecordingStatus } from '../../models/recording.model';
import { ActionService } from '../../services/action/action.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
import { OpenViduComponentsConfigService } from '../../services/config/openvidu-components-angular.config.service';
import { RecordingService } from '../../services/recording/recording.service';
@Component({
selector: 'ov-admin-dashboard',
templateUrl: './admin-dashboard.component.html',
styleUrls: ['./admin-dashboard.component.scss'],
standalone: false
styleUrls: ['./admin-dashboard.component.scss']
})
export class AdminDashboardComponent implements OnInit, OnDestroy {
/**
@ -36,11 +35,6 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
*/
@Output() onLogoutRequested: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
*/
title = '';
/**
* @internal
*/
@ -61,9 +55,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
* @internal
*/
recordingStatusEnum = RecordingStatus;
private recordingsSub: Subscription;
private titleSub: Subscription;
private adminSubscription: Subscription;
/**
* @internal
*/
@ -84,8 +76,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
* @internal
*/
ngOnDestroy() {
if (this.recordingsSub) this.recordingsSub.unsubscribe();
if (this.titleSub) this.titleSub.unsubscribe();
if (this.adminSubscription) this.adminSubscription.unsubscribe();
}
/**
@ -254,7 +245,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
}
private subscribeToAdminDirectives() {
this.recordingsSub = this.libService.adminRecordingsList$.subscribe((recordings: RecordingInfo[]) => {
this.adminSubscription = this.libService.adminRecordingsList$.subscribe((recordings: RecordingInfo[]) => {
// Remove the recordings that are marked for deletion
this.filterDeletedRecordings(recordings);
@ -264,9 +255,5 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
this.sortRecordings();
});
this.titleSub = this.libService.adminDashboardTitle$.subscribe((value) => {
this.title = value;
});
}
}

View File

@ -1,4 +1,4 @@
<mat-toolbar class="header"> {{ title || ('ADMIN.DASHBOARD_TITLE' | translate) }} </mat-toolbar>
<mat-toolbar class="header"> {{ 'ADMIN.DASHBOARD_TITLE' | translate }} </mat-toolbar>
<div class="center-container">
<div *ngIf="loading" class="outer">

View File

@ -6,5 +6,4 @@ With the following directives you can modify the default User Interface with the
| **Parameter** | **Type** | **Reference** |
|:--------------------------------: | :-------: | :---------------------------------------------: |
| **error** | `any` | [AdminLoginErrorDirective](../directives/AdminLoginErrorDirective.html) |
| **navbarTitle** | `any` | [AdminLoginTitleDirective](../directives/AdminLoginTitleDirective.html) |
<!-- end-dynamic-api-directives-content -->

View File

@ -1,7 +1,7 @@
.header {
height: 50px;
background-color: var(--ov-background-color);
color: var(--ov-text-primary-color);
background-color: var(--ov-primary-color);
color: var(--ov-text-color);
}
.center-container {
@ -9,12 +9,12 @@
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--ov-secondary-action-color);
background-color: var(--ov-secondary-color);
}
.card-container {
text-align: center;
background-color: var(--ov-surface-color);
background-color: var(--ov-panel-background);
padding: 10px;
max-width: 300px;
min-width: 300px;
@ -23,8 +23,8 @@
}
.form-btn {
background-color: var(--ov-accent-action-color) !important;
color: var(--ov-text-primary-color) !important;
background-color: var(--ov-tertiary-color) !important;
color: var(--ov-text-color) !important;
}
.form-field,
@ -33,5 +33,5 @@
}
::ng-deep .mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: var(--ov-accent-action-color);
--mdc-circular-progress-active-indicator-color: var(--ov-tertiary-color);
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminLoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: AdminLoginComponent;
let fixture: ComponentFixture<AdminLoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AdminLoginComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AdminLoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -2,13 +2,12 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Validators, FormGroup, FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { ActionService } from '../../services/action/action.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
import { OpenViduComponentsConfigService } from '../../services/config/openvidu-components-angular.config.service';
@Component({
selector: 'ov-admin-login',
templateUrl: './admin-login.component.html',
styleUrls: ['./admin-login.component.scss'],
standalone: false
styleUrls: ['./admin-login.component.scss']
})
export class AdminLoginComponent implements OnInit {
/**
@ -21,11 +20,6 @@ export class AdminLoginComponent implements OnInit {
password: string;
}>();
/**
* @internal
*/
title: string;
/**
* @internal
*/
@ -42,7 +36,6 @@ export class AdminLoginComponent implements OnInit {
loginForm: FormGroup;
private errorSub: Subscription;
private titleSub: Subscription;
/**
* @internal
@ -71,7 +64,6 @@ export class AdminLoginComponent implements OnInit {
ngOnDestroy() {
this.showSpinner = false;
if (this.errorSub) this.errorSub.unsubscribe();
if (this.titleSub) this.titleSub.unsubscribe();
}
/**
@ -91,9 +83,5 @@ export class AdminLoginComponent implements OnInit {
this.actionService.openDialog(value.error, value.message, true);
}
});
this.titleSub = this.libService.adminLoginTitle$.subscribe((value) => {
this.title = value;
});
}
}

View File

@ -23,13 +23,13 @@
}
.audio-container{
background-color: var(--ov-accent-action-color);
background-color: var(--ov-tertiary-color);
padding: 5px;
max-width: 15px;
max-height: 15px;
height: 15px;
width: 15px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-video-radius);
display: flex;
justify-content: space-between;
}
@ -38,7 +38,7 @@
margin: auto;
height: 80%;
width: 3px;
background: var(--ov-secondary-action-color);
background: var(--ov-text-color);
border-radius: 8px;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AudioWaveComponent } from './audio-wave.component';
describe('AudioWaveComponent', () => {
let component: AudioWaveComponent;
let fixture: ComponentFixture<AudioWaveComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AudioWaveComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AudioWaveComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -10,7 +10,6 @@ import { Component } from '@angular/core';
<div class="stick loud play"></div>
<div class="stick normal play"></div>
</div>`,
styleUrls: ['./audio-wave.component.scss'],
standalone: false
styleUrls: ['./audio-wave.component.scss']
})
export class AudioWaveComponent {}

View File

@ -19,7 +19,7 @@
height: 70px;
width: 70px;
border-radius: var(--ov-video-radius);
border: 2px solid var(--ov-text-primary-color);
border: 2px solid var(--ov-text-color);
color: #000000;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AvatarProfileComponent } from './avatar-profile.component';
describe('AvatarProfileComponent', () => {
let component: AvatarProfileComponent;
let fixture: ComponentFixture<AvatarProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AvatarProfileComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AvatarProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -13,8 +13,7 @@ import { Component, Input } from '@angular/core';
</div>
</div>
`,
styleUrls: ['./avatar-profile.component.scss'],
standalone: false
styleUrls: ['./avatar-profile.component.scss']
})
export class AvatarProfileComponent {
letter: string;

View File

@ -29,7 +29,7 @@
.error-container {
display: grid;
text-align: center;
color: var(--ov-text-primary-color);
color: var(--ov-text-color);
font-size: 18px;
}
@ -43,7 +43,7 @@ mat-spinner {
}
::ng-deep .mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: var(--ov-accent-action-color);
--mdc-circular-progress-active-indicator-color: var(--ov-tertiary-color);
}
/*
@ -160,8 +160,8 @@ mat-spinner {
}
#caption-settings-btn {
color: var(--ov-text-primary-color);
background-color: var(--ov-secondary-action-color);
color: var(--ov-text-color);
background-color: var(--ov-secondary-color);
}
#caption-settings-icon {
font-size: 15px;
@ -190,12 +190,12 @@ mat-spinner {
.caption-text,
#speaker {
color: var(--ov-text-primary-color);
color: var(--ov-text-color);
font-family: 'Roboto', arial, sans-serif;
}
.caption-text {
background-color: var(--ov-secondary-action-color);
background-color: var(--ov-logo-background-color);
padding: 4.5px;
line-height: 1.6;
word-break: break-word;

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CaptionsComponent } from './captions.component';
describe('CaptionsComponent', () => {
let component: CaptionsComponent;
let fixture: ComponentFixture<CaptionsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CaptionsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CaptionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -9,35 +9,11 @@ import { MatDialogRef } from '@angular/material/dialog';
template: `
<div mat-dialog-content>{{'PANEL.RECORDING.DELETE_QUESTION' | translate}}</div>
<div mat-dialog-actions>
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.RECORDING.CANCEL' | translate }}</button>
<button [disableRipple]="true" mat-button cdkFocusInitial (click)="close(true)" id="delete-recording-confirm-btn">
{{ 'PANEL.RECORDING.DELETE' | translate }}
</button>
<button mat-button (click)="close()">{{'PANEL.RECORDING.CANCEL' | translate }}</button>
<button mat-button cdkFocusInitial (click)="close(true)" id="delete-recording-confirm-btn">{{'PANEL.RECORDING.DELETE' | translate}}</button>
</div>
`,
styles: [
`
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
#delete-recording-confirm-btn {
background-color: var(--ov-error-color) !important;
color: var(--ov-secondary-action-color);
}
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
`
],
standalone: false
styles: [``]
})
export class DeleteDialogComponent {
constructor(public dialogRef: MatDialogRef<DeleteDialogComponent>) {}

View File

@ -12,35 +12,12 @@ import { DialogData } from '../../models/dialog.model';
<h1 mat-dialog-title>{{ data.title }}</h1>
<div mat-dialog-content id="openvidu-dialog">{{ data.description }}</div>
<div mat-dialog-actions *ngIf="data.showActionButtons">
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button>
<button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button>
</div>
`,
styles: [
`
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
`
],
standalone: false
})
export class DialogTemplateComponent {
constructor(
public dialogRef: MatDialogRef<DialogTemplateComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData
) {}
constructor(public dialogRef: MatDialogRef<DialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
close() {
this.dialogRef.close();

View File

@ -19,8 +19,7 @@ import { DialogData } from '../../models/dialog.model';
</button>
<button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button>
</div>
`,
standalone: false
`
})
export class ProFeatureDialogTemplateComponent {
constructor(public dialogRef: MatDialogRef<ProFeatureDialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
@ -30,6 +29,6 @@ export class ProFeatureDialogTemplateComponent {
}
seeMore() {
window.open('https://openvidu.io/pricing/#openvidu-pro', '_blank')?.focus();
window.open('https://docs.openvidu.io/en/stable/openvidu-pro/', '_blank')?.focus();
}
}

View File

@ -12,34 +12,17 @@ import { RecordingDialogData } from '../../models/dialog.model';
<video #videoElement controls autoplay [src]="src" (error)="handleError()"></video>
</div>
<div mat-dialog-actions *ngIf="data.showActionButtons" align="end">
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button>
<button mat-button (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button>
</div>
`,
styles: [
`
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
video {
max-height: 64vh;
max-width: 100%;
}
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
`
],
standalone: false
]
})
export class RecordingDialogComponent {
@ViewChild('videoElement', { static: true }) videoElement: ElementRef<HTMLVideoElement>;

View File

@ -19,11 +19,6 @@
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
</div>
<!-- Render additional layout elements injected via ovAdditionalLayoutElement -->
@if (layoutAdditionalElementsTemplate) {
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
}
<div
*ngFor="let track of remoteParticipants | tracks; trackBy: trackParticipantElement"
class="remote-participant"

View File

@ -31,7 +31,7 @@
.OV_root,
.OV_root * {
color: var(--ov-secondary-action-color);
color: #ffffff;
margin: 0;
padding: 0;
border: 0;

View File

@ -0,0 +1,37 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemoteUserService, LocalUserService } from '../../../public-api';
import { ChatService } from '../../services/chat/chat.service';
import { ChatServiceMock } from '../../services/chat/chat.service.mock';
import { LayoutService } from '../../services/layout/layout.service';
import { LocalUserServiceMock } from '../../services/local-user/local-user.service.mock';
import { RemoteUserServiceMock } from '../../services/remote-user/remote-user.service.mock';
import { LayoutComponent } from './layout.component';
describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LayoutComponent ],
providers: [
{ provide: RemoteUserService, useClass: RemoteUserServiceMock },
{ provide: LocalUserService, useClass: LocalUserServiceMock },
{ provide: ChatService, useClass: ChatServiceMock },
{ provide: LayoutService, useClass: LayoutService }
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,5 +1,3 @@
import { LayoutAdditionalElementsDirective } from '../../directives/template/internals.directive';
import {
AfterViewInit,
ChangeDetectionStrategy,
@ -13,16 +11,14 @@ import {
ViewChild,
ViewContainerRef
} from '@angular/core';
import { combineLatest, map, Subject, takeUntil } from 'rxjs';
import { Subscription } from 'rxjs';
import { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model';
import { LayoutService } from '../../services/layout/layout.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { CdkDrag } from '@angular/cdk/drag-drop';
import { PanelService } from '../../services/panel/panel.service';
import { GlobalConfigService } from '../../services/config/global-config.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
import { LayoutTemplateConfiguration, TemplateManagerService } from '../../services/template/template-manager.service';
import { OpenViduComponentsConfigService } from '../../services/config/openvidu-components-angular.config.service';
/**
*
@ -33,8 +29,7 @@ import { LayoutTemplateConfiguration, TemplateManagerService } from '../../servi
selector: 'ov-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
/**
@ -42,11 +37,6 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
*/
@ContentChild('stream', { read: TemplateRef }) streamTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('layoutAdditionalElements', { read: TemplateRef }) layoutAdditionalElementsTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ -70,27 +60,9 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
// is inside of the layout component tagged with '*ovLayout' directive
if (externalStream) {
this.streamTemplate = externalStream.template;
this.updateTemplatesAndMarkForCheck();
}
}
/**
* @ignore
*/
@ContentChild(LayoutAdditionalElementsDirective) set externalAdditionalElements(
externalAdditionalElements: LayoutAdditionalElementsDirective
) {
if (externalAdditionalElements) {
this._externalLayoutAdditionalElements = externalAdditionalElements;
this.updateTemplatesAndMarkForCheck();
}
}
/**
* @ignore
*/
templateConfig: LayoutTemplateConfiguration = {};
localParticipant: ParticipantModel | undefined;
remoteParticipants: ParticipantModel[] = [];
/**
@ -98,11 +70,11 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
*/
captionsEnabled = true;
private _externalStream?: StreamDirective;
private _externalLayoutAdditionalElements?: LayoutAdditionalElementsDirective;
private destroy$ = new Subject<void>();
private localParticipantSubs: Subscription;
private remoteParticipantsSubs: Subscription;
private captionsSubs: Subscription;
private resizeObserver: ResizeObserver;
private cdkSubscription: Subscription;
private resizeTimeout: NodeJS.Timeout;
private videoIsAtRight: boolean = false;
private lastLayoutWidth: number = 0;
@ -114,21 +86,16 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
private layoutService: LayoutService,
private panelService: PanelService,
private participantService: ParticipantService,
private globalService: GlobalConfigService,
private directiveService: OpenViduComponentsConfigService,
private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
private libService: OpenViduComponentsConfigService,
private cd: ChangeDetectorRef
) {}
ngOnInit(): void {
this.setupTemplates();
this.subscribeToParticipants();
this.subscribeToCaptions();
}
ngAfterViewInit() {
console.log('LayoutComponent.ngAfterViewInit');
this.layoutService.initialize(this.layoutContainer.element.nativeElement);
this.lastLayoutWidth = this.layoutContainer.element.nativeElement.getBoundingClientRect().width;
this.listenToResizeLayout();
@ -136,11 +103,13 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.localParticipant = undefined;
this.remoteParticipants = [];
this.resizeObserver?.disconnect();
this.localParticipantSubs?.unsubscribe();
this.remoteParticipantsSubs?.unsubscribe();
this.captionsSubs?.unsubscribe();
this.cdkSubscription?.unsubscribe();
this.layoutService.clear();
}
@ -153,36 +122,8 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
return track;
}
private setupTemplates() {
this.templateConfig = this.templateManagerService.setupLayoutTemplates(
this._externalStream,
this._externalLayoutAdditionalElements
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
private applyTemplateConfiguration() {
if (this.templateConfig.layoutStreamTemplate) {
this.streamTemplate = this.templateConfig.layoutStreamTemplate;
}
if (this.templateConfig.layoutAdditionalElementsTemplate) {
this.layoutAdditionalElementsTemplate = this.templateConfig.layoutAdditionalElementsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
private subscribeToCaptions() {
this.layoutService.captionsTogglingObs.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value;
this.cd.markForCheck();
this.layoutService.update();
@ -190,7 +131,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
}
private subscribeToParticipants() {
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe((p) => {
this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p) => {
if (p) {
this.localParticipant = p;
if (!this.localParticipant?.isMinimized) {
@ -201,14 +142,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
}
});
combineLatest([this.participantService.remoteParticipants$, this.directiveService.layoutRemoteParticipants$])
.pipe(
map(([serviceParticipants, directiveParticipants]) =>
directiveParticipants !== undefined ? directiveParticipants : serviceParticipants
),
takeUntil(this.destroy$)
)
.subscribe((participants) => {
this.remoteParticipantsSubs = this.participantService.remoteParticipants$.subscribe((participants) => {
this.remoteParticipants = participants;
this.layoutService.update();
this.cd.markForCheck();
@ -271,10 +205,9 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
this.videoIsAtRight = false;
}
};
this.cdkSubscription = this.cdkDrag.released.subscribe(handler);
this.cdkDrag.released.pipe(takeUntil(this.destroy$)).subscribe(handler);
if (this.globalService.isProduction()) return;
if (this.libService.getConfig().production) return;
// Just for allow E2E testing with drag and drop
document.addEventListener('webcomponentTestingEndedDragAndDropEvent', handler);
document.addEventListener('webcomponentTestingEndedDragAndDropRightEvent', (event: any) => {

View File

@ -3,7 +3,7 @@ video {
object-fit: cover;
width: 100%;
height: 100%;
color: var(--ov-text-primary-color);
color: #ffffff;
margin: 0;
padding: 0;
border: 0;

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VideoComponent } from './media-element.component';
describe('VideoComponent', () => {
let component: VideoComponent;
let fixture: ComponentFixture<VideoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ VideoComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(VideoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -18,8 +18,7 @@ import { Track } from 'livekit-client';
transition(':enter', [style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]),
transition(':leave', [style({ opacity: 1 }), animate('200ms', style({ opacity: 0 }))])
])
],
standalone: false
]
})
export class MediaElementComponent implements AfterViewInit {
_track: Track;

View File

@ -17,8 +17,6 @@
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
></ov-recording-activity>
<ov-broadcasting-activity
*ngIf="showBroadcastingActivity"

View File

@ -4,6 +4,6 @@ With the following directives you can modify the default User Interface with the
<!-- start-dynamic-api-directives-content -->
| **Parameter** | **Type** | **Reference** |
|:--------------------------------: | :-------: | :---------------------------------------------: |
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
| **recordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) |
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
<!-- end-dynamic-api-directives-content -->

View File

@ -1,5 +1,3 @@
$ov-activity-status-color: #afafaf;
:host{
.activities-body-container {
display: block !important;
@ -8,26 +6,16 @@ $ov-activity-status-color: #afafaf;
padding: 10px;
}
.activity-status {
color: var(--ov-text-surface-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-surface-radius);
background-color: $ov-activity-status-color;
}
.activity-icon {
display: inherit;
background-color: $ov-activity-status-color;;
border-radius: var(--ov-surface-radius);
background-color: var(--ov-light-color);
border-radius: var(--ov-panel-radius);
margin: 10px 0px !important;
padding: 10px;
}
.activity-title,
.activity-subtitle {
color: var(--ov-text-surface-color);
.activity-title, .activity-subtitle {
color: var(--ov-panel-text-color);
}
.activity-subtitle {
@ -58,11 +46,6 @@ $ov-activity-status-color: #afafaf;
::ng-deep .mat-expansion-panel-header {
padding: 0px 5px !important;
height: 65px !important;
cursor: pointer !important;
}
::ng-deep .mat-expansion-panel-header:hover {
background: none !important;
}
::ng-deep .mat-mdc-list-base .mat-mdc-list-item .mat-list-item-content,
@ -70,10 +53,6 @@ $ov-activity-status-color: #afafaf;
padding: 0px !important;
}
::ng-deep mat-list mat-list-item {
cursor: pointer !important;
}
::ng-deep mat-expansion-panel .mat-expansion-panel-body {
padding: 0px !important;
min-height: 400px;
@ -84,16 +63,14 @@ $ov-activity-status-color: #afafaf;
::ng-deep .mat-expansion-panel {
box-shadow: none !important;
background-color: var(--ov-surface-color) !important;
background-color: var(--ov-panel-background) !important;
}
::ng-deep .no-body .mat-expansion-panel-content {
display: none !important;
}
::ng-deep
.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta
.mdc-list-item__end::before {
::ng-deep .mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{
max-height: 24px;
height: 24px;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivitiesPanelComponent } from './activities-panel.component';
describe('ActivitiesPanelComponent', () => {
let component: ActivitiesPanelComponent;
let fixture: ComponentFixture<ActivitiesPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ActivitiesPanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ActivitiesPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Subscription } from 'rxjs';
import { PanelStatusInfo, PanelType } from '../../../models/panel.model';
import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service';
import { OpenViduComponentsConfigService } from '../../../services/config/openvidu-components-angular.config.service';
import { PanelService } from '../../../services/panel/panel.service';
import {
RecordingDeleteRequestedEvent,
@ -20,8 +20,7 @@ import { BroadcastingStartRequestedEvent, BroadcastingStopRequestedEvent } from
selector: 'ov-activities-panel',
templateUrl: './activities-panel.component.html',
styleUrls: ['../panel.component.scss', './activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ActivitiesPanelComponent implements OnInit {
/**
@ -54,21 +53,6 @@ export class ActivitiesPanelComponent implements OnInit {
*/
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
/**
* @internal
* Provides event notifications that fire when view recordings button has been clicked.
* This event is triggered when the user wants to view all recordings in an external page.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
* Provides event notifications that fire when view recording button has been clicked.
* This event is triggered when the user wants to view a specific recording in an external page.
* It provides the recording ID as event data.
*/
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* Provides event notifications that fire when start broadcasting button is clicked.
* It provides the {@link BroadcastingStartRequestedEvent} payload as event data.
@ -95,7 +79,9 @@ export class ActivitiesPanelComponent implements OnInit {
* @internal
*/
showBroadcastingActivity: boolean = true;
private destroy$ = new Subject<void>();
private panelSubscription: Subscription;
private recordingActivitySub: Subscription;
private broadcastingActivitySub: Subscription;
/**
* @internal
@ -118,8 +104,9 @@ export class ActivitiesPanelComponent implements OnInit {
* @internal
*/
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
if (this.panelSubscription) this.panelSubscription.unsubscribe();
if (this.recordingActivitySub) this.recordingActivitySub.unsubscribe();
if (this.broadcastingActivitySub) this.broadcastingActivitySub.unsubscribe();
}
/**
@ -130,7 +117,7 @@ export class ActivitiesPanelComponent implements OnInit {
}
private subscribeToPanelToggling() {
this.panelService.panelStatusObs.pipe(takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
this.panelSubscription = this.panelService.panelStatusObs.subscribe((ev: PanelStatusInfo) => {
if (ev.panelType === PanelType.ACTIVITIES && !!ev.subOptionType) {
this.expandedPanel = ev.subOptionType;
}
@ -138,12 +125,12 @@ export class ActivitiesPanelComponent implements OnInit {
}
private subscribeToActivitiesPanelDirective() {
this.libService.recordingActivity$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.recordingActivitySub = this.libService.recordingActivity$.subscribe((value: boolean) => {
this.showRecordingActivity = value;
this.cd.markForCheck();
});
this.libService.broadcastingActivity$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.broadcastingActivitySub = this.libService.broadcastingActivity$.subscribe((value: boolean) => {
this.showBroadcastingActivity = value;
this.cd.markForCheck();
});

View File

@ -1,9 +1,4 @@
<mat-expansion-panel
(opened)="setPanelOpened(true)"
(closed)="setPanelOpened(false)"
[expanded]="expanded"
[ngClass]="{ 'no-body': !isPanelOpened }"
>
<mat-expansion-panel (opened)="setPanelOpened(true)" (closed)="setPanelOpened(false)" [expanded]="expanded" [ngClass]="{ 'no-body': !isPanelOpened }">
<mat-expansion-panel-header>
<mat-list>
<mat-list-item>
@ -14,7 +9,7 @@
started: broadcastingStatus === broadcastingStatusEnum.STARTED,
stopped: broadcastingStatus === broadcastingStatusEnum.STOPPED,
failed: broadcastingStatus === broadcastingStatusEnum.FAILED,
starting:
pending:
broadcastingStatus === broadcastingStatusEnum.STARTING || broadcastingStatus === broadcastingStatusEnum.STOPPING
}"
>
@ -34,12 +29,11 @@
<div class="activity-action-buttons" matListItemMeta>
<div
id="broadcasting-status"
class="activity-status"
[ngClass]="{
started: broadcastingStatus === broadcastingStatusEnum.STARTED,
stopped: broadcastingStatus === broadcastingStatusEnum.STOPPED,
failed: broadcastingStatus === broadcastingStatusEnum.FAILED,
starting:
pending:
broadcastingStatus === broadcastingStatusEnum.STARTING ||
broadcastingStatus === broadcastingStatusEnum.STOPPING
}"

View File

@ -1,26 +1,29 @@
$ov-broadcasting-color: #5903ca;
$ov-input-color: #cccccc;
#broadcasting-status {
color: var(--ov-text-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
.time-container {
padding: 2px;
}
.error-text {
color: var(--ov-error-color);
color: var(--ov-warn-color);
font-style: italic;
font-size: 14px;
}
#broadcasting-icon {
color: $ov-broadcasting-color !important;
color: #5903ca;
}
.broadcasting-duration {
background-color: var(--ov-secondary-action-color);
background-color: var(--ov-light-color);
padding: 4px 8px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
font-weight: 500;
}
@ -31,27 +34,27 @@ $ov-input-color: #cccccc;
}
.started {
background-color: $ov-broadcasting-color !important;
color: var(--ov-text-primary-color);
background-color: #5903ca !important;
color: var(--ov-text-color);
}
.activity-icon.started {
background-color: $ov-broadcasting-color !important;
color: var(--ov-text-primary-color);
background-color: #5903ca !important;
color: var(--ov-text-color);
}
.failed {
background-color: var(--ov-error-color) !important;
color: var(--ov-text-primary-color);
background-color: var(--ov-warn-color) !important;
color: var(--ov-text-color);
}
.stopped {
background-color: var(--ov-secondary-action-color);
color: var(--ov-text-surface-color) !important;
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color) !important;
}
.starting {
background-color: var(--ov-warn-color) !important;
color: var(--ov-text-primary-color) !important;
.pending {
background-color: #ffd79b !important;
color: var(--ov-panel-text-color) !important;
}
.panel-body-container {
@ -69,7 +72,7 @@ $ov-input-color: #cccccc;
}
.broadcasting-error {
color: var(--ov-error-color);
color: var(--ov-warn-color);
font-weight: 600;
}
.broadcasting-name {
@ -89,13 +92,13 @@ $ov-input-color: #cccccc;
/* #start-broadcasting-btn {
width: 100%;
background-color: var(--ov-accent-action-color);
color: var(--ov-text-primary-color);
background-color: var(--ov-tertiary-color);
color: var(--ov-text-color);
} */
#stop-broadcasting-btn {
/* background-color: var(--ov-error-color); */
color: var(--ov-error-color);
/* background-color: var(--ov-warn-color); */
color: var(--ov-warn-color);
}
#reset-broadcasting-status-btn {
@ -119,10 +122,10 @@ mat-expansion-panel {
.input-container {
height: 25px;
display: flex;
background-color: $ov-input-color;
background-color: var(--ov-light-color);
padding: 10px;
margin: 10px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
order: 3;
justify-content: space-evenly;
align-items: center;
@ -140,6 +143,7 @@ mat-expansion-panel {
white-space: pre-wrap;
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BroadcastingActivityComponent } from './broadcasting-activity.component';
describe('BroadcastingActivityComponent', () => {
let component: BroadcastingActivityComponent;
let fixture: ComponentFixture<BroadcastingActivityComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BroadcastingActivityComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(BroadcastingActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Subscription } from 'rxjs';
import {
BroadcastingStartRequestedEvent,
BroadcastingStatus,
@ -18,8 +18,7 @@ import { OpenViduService } from '../../../../services/openvidu/openvidu.service'
selector: 'ov-broadcasting-activity',
templateUrl: './broadcasting-activity.component.html',
styleUrls: ['./broadcasting-activity.component.scss', '../activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
// TODO: Allow to add more than one broadcast url
@ -76,7 +75,7 @@ export class BroadcastingActivityComponent implements OnInit {
*/
isPanelOpened: boolean = false;
private destroy$ = new Subject<void>();
private broadcastingSub: Subscription;
/**
* @internal
@ -99,8 +98,7 @@ export class BroadcastingActivityComponent implements OnInit {
* @internal
*/
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
if (this.broadcastingSub) this.broadcastingSub.unsubscribe();
}
/**
@ -148,7 +146,7 @@ export class BroadcastingActivityComponent implements OnInit {
}
private subscribeToBroadcastingStatus() {
this.broadcastingService.broadcastingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: BroadcastingStatusInfo | undefined) => {
this.broadcastingSub = this.broadcastingService.broadcastingStatusObs.subscribe((event: BroadcastingStatusInfo | undefined) => {
if (!!event) {
const { status, broadcastingId, error } = event;
this.broadcastingStatus = status;

View File

@ -1,8 +1,10 @@
:host {
$ov-activity-status-color: #afafaf;
.recording-title,
.recording-subtitle {
color: var(--ov-text-surface-color);
#recording-status {
color: var(--ov-text-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
.recording-title {
@ -21,15 +23,14 @@
flex-wrap: wrap;
height: auto;
align-content: center;
color: var(--ov-text-surface-color) !important;
color: var(--ov-panel-text-color) !important;
}
.recording-duration {
background-color: $ov-activity-status-color;
background-color: var(--ov-light-color);
padding: 4px 8px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
font-weight: 500;
color: var(--ov-text-surface-color);
}
.recording-duration mat-icon {
@ -40,22 +41,22 @@
.started {
background-color: #3b7430 !important;
color: #ffffff !important;
color: var(--ov-text-color);
}
.activity-icon.started,
.failed {
background-color: var(--ov-error-color) !important;
// color: var(--ov-secondary-action-color);
background-color: var(--ov-warn-color) !important;
color: var(--ov-text-color);
}
.stopped {
// background-color: var(--ov-secondary-action-color);
color: var(--ov-text-surface-color) !important;
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color) !important;
}
.starting {
background-color: var(--ov-warn-color) !important;
color: #000000 !important;
.pending {
background-color: #ffd79b !important;
color: var(--ov-panel-text-color) !important;
}
.panel-body-container {
@ -72,458 +73,18 @@
text-align: center;
}
.recording-placeholder {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.recording-placeholder-img {
max-width: 100%;
height: auto;
border-radius: 8px;
}
.empty-state {
margin-bottom: 20px;
}
.recording-status-messages {
margin-top: 10px;
}
.recording-status {
display: flex;
align-items: flex-start;
gap: 12px;
border: 1px solid var(--ov-warn-color);
border-radius: 8px;
padding: 12px 16px;
margin: 16px 0;
font-size: 15px;
box-shadow: 0 2px 8px 0 rgba(255, 193, 7, 0.04);
.status-icon {
font-size: 28px;
.recording-error {
color: var(--ov-warn-color);
flex-shrink: 0;
margin-top: 2px;
}
.status-content {
display: flex;
flex-direction: column;
gap: 2px;
}
.status-title {
font-weight: 600;
font-size: 15px;
margin-bottom: 2px;
}
.status-message {
font-size: 14px;
opacity: 0.85;
}
}
.recording-status-starting {
background: rgba(255, 193, 7, 0.08);
border-color: var(--ov-warn-color);
}
.recording-status-stopping {
background: rgba(255, 193, 7, 0.13);
border-color: var(--ov-warn-color);
}
.recording-error-container {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 10px;
}
// Modern recording list styles
.recording-list-container {
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 16px;
max-height: 500px;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: var(--ov-accent-action-color);
border-radius: 2px;
opacity: 0.3;
}
&::-webkit-scrollbar-thumb:hover {
opacity: 0.6;
}
}
.recording-card {
background: var(--ov-surface-background-color);
border: 1px solid rgba(0, 102, 204, 0.1);
border-radius: var(--ov-surface-radius);
padding: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
width: 100%;
display: flex;
flex-direction: column;
flex-shrink: 0;
box-sizing: border-box;
&.recording-active {
background: linear-gradient(135deg, transparent 69%, var(--ov-error-color) 250%);
}
}
.recording-header {
display: flex;
align-items: flex-start;
gap: 5px;
width: 100%;
height: 60px;
flex-shrink: 0;
}
.recording-status-indicator {
flex-shrink: 0;
padding-top: 2px;
width: 16px;
height: 16px;
display: flex;
justify-content: center;
align-items: flex-start;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
&.recording-live {
background: #ffffff;
box-shadow: 0 0 0 4px var(--ov-error-color);
animation: pulse-dot 2s infinite;
}
&.recording-stopping {
background: var(--ov-warn-color);
animation: pulse-dot 2s infinite;
}
&.recording-failed {
background: var(--ov-error-color);
}
&.recording-ready {
background: #4caf50;
}
}
.recording-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
overflow: hidden;
}
.recording-name {
font-size: 14px;
font-weight: 500;
color: var(--ov-text-surface-color);
margin-bottom: 4px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 17px;
font-weight: bold;
}
.recording-status-text {
font-size: 12px;
font-weight: 500;
&.recording-live-text {
color: var(--ov-primary-action-color);
text-transform: uppercase;
letter-spacing: 0.5px;
}
}
.recording-metadata {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-top: 4px;
height: auto;
overflow: visible;
}
.metadata-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--ov-text-surface-color);
opacity: 0.7;
white-space: nowrap;
flex-shrink: 0;
.metadata-icon {
font-size: 14px;
width: 14px;
height: 14px;
flex-shrink: 0;
}
}
.recording-actions-menu {
display: flex;
gap: 8px;
flex-shrink: 0;
opacity: 1;
align-items: center;
width: 100%;
justify-content: center;
height: 32px;
margin-top: auto;
}
.action-btn {
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
&.action-play {
color: var(--ov-accent-action-color);
&:hover {
background: rgba(0, 102, 204, 0.1);
color: var(--ov-accent-action-color);
}
}
&.action-view {
color: var(--ov-accent-action-color);
border-radius: var(--ov-surface-radius);
}
&.action-download {
color: #4caf50;
&:hover {
background: rgba(76, 175, 80, 0.1);
color: #4caf50;
}
}
&.action-delete {
color: var(--ov-error-color);
&:hover {
background: rgba(244, 67, 54, 0.1);
color: var(--ov-error-color);
}
}
}
// Animations
@keyframes pulse-dot {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.8;
}
}
@keyframes pulse-border {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.recording-actions {
display: flex;
gap: 5px;
}
.action-button {
transition: all 0.2s ease;
&:hover {
transform: scale(1.1);
}
}
// Mobile responsive design for new recording cards
@media (max-width: 768px) {
.recording-list-container {
padding-top: 12px;
gap: 12px;
}
.recording-card {
padding: 8px;
height: 100px;
gap: 8px;
}
.recording-header {
gap: 8px;
height: 50px;
}
.recording-info {
min-width: 0;
}
.recording-metadata {
gap: 8px;
margin-top: 2px;
}
.metadata-item {
font-size: 11px;
gap: 2px;
.metadata-icon {
font-size: 12px;
width: 12px;
height: 12px;
}
}
.recording-actions-menu {
opacity: 1; // Always visible on mobile
gap: 6px;
height: 28px;
}
.action-btn {
width: 28px;
height: 28px;
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
}
}
}
.recording-message {
color: var(--ov-text-surface-color);
}
.recording-error {
color: var(--ov-error-color);
font-weight: 600;
}
.recording-error {
display: flex;
align-items: flex-start;
gap: 12px;
background: rgba(244, 67, 54, 0.08);
border: 1px solid var(--ov-error-color);
border-radius: 8px;
padding: 12px 16px;
margin: 16px 0;
color: var(--ov-error-color);
font-size: 15px;
box-shadow: 0 2px 8px 0 rgba(244, 67, 54, 0.04);
.error-icon {
font-size: 28px;
color: var(--ov-error-color);
flex-shrink: 0;
margin-top: 2px;
width: 100%;
height: 100%;
}
.error-content {
display: flex;
flex-direction: column;
gap: 2px;
}
.error-title {
font-weight: 600;
font-size: 15px;
margin-bottom: 2px;
}
.error-message {
font-size: 14px;
opacity: 0.85;
}
}
.disable-recording-btn {
background-color: var(--ov-secondary-action-color) !important;
color: var(--ov-text-surface-color) !important;
cursor: not-allowed !important;
}
// Enhanced empty state
.empty-state {
text-align: center;
padding: 32px 16px;
color: var(--ov-text-surface-color);
}
.empty-state-icon {
margin-bottom: 16px;
mat-icon {
font-size: 48px;
width: 48px;
height: 48px;
color: var(--ov-accent-action-color);
opacity: 0.6;
}
}
.empty-state-title {
font-size: 18px;
font-weight: 500;
margin: 0 0 8px 0;
color: var(--ov-text-surface-color);
}
.empty-state-subtitle {
font-size: 14px;
margin: 0;
opacity: 0.7;
line-height: 1.4;
.recording-date {
font-size: 12px !important;
font-style: italic;
}
.not-allowed-message {
@ -532,44 +93,29 @@
}
.recording-action-buttons {
margin: 5px 0px;
margin-top: 20px;
margin-bottom: 20px;
}
#start-recording-btn {
width: 100%;
background-color: var(--ov-primary-action-color);
color: var(--ov-secondary-action-color);
border-radius: var(--ov-surface-radius);
}
#view-recordings-btn {
width: 100%;
background-color: var(--ov-accent-action-color);
color: var(--ov-secondary-action-color);
border-radius: var(--ov-surface-radius);
margin-bottom: 10px;
mat-icon {
margin-right: 8px;
}
}
.start-recording-button-container {
width: 100%;
display: inline-block;
background-color: var(--ov-tertiary-color);
color: var(--ov-text-color);
}
#stop-recording-btn {
width: 100%;
background-color: var(--ov-error-color);
color: var(--ov-secondary-action-color);
border-radius: var(--ov-surface-radius);
background-color: var(--ov-warn-color);
color: var(--ov-text-color);
}
.delete-recording-btn {
color: var(--ov-warn-color);
}
#reset-recording-status-btn {
width: 100%;
background-color: var(--ov-accent-action-color);
border-radius: var(--ov-surface-radius);
background-color: var(--ov-light-color);
}
.recording-item {
@ -587,7 +133,6 @@
width: 40px !important;
height: 40px !important;
padding: 5px !important;
color: var(--ov-text-surface-color);
}
#play-recording-btn > .mat-icon,
#download-recording-btn > .mat-icon,
@ -595,13 +140,6 @@
height: 20px !important;
}
#delete-recording-btn {
color: var(--ov-error-color);
}
#download-recording-btn {
color: var(--ov-accent-action-color);
}
mat-expansion-panel {
margin: 0px 0px 5px 0px;
}
@ -618,18 +156,6 @@
height: 0px !important;
}
::ng-deep .mdc-list-item__secondary-text,
::ng-deep .mdc-list-item__primary-text {
color: var(--ov-text-surface-color);
}
// ::ng-deep .mat-mdc-list-item:hover {
// color: #000000 !important;
// }
::ng-deep .mat-mdc-list-item:hover .mat-mdc-list-item-title {
color: var(--ov-text-surface-color) !important;
}
.blink {
animation: blinker 1.5s linear infinite !important;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RecordingActivityComponent } from './recording-activity.component';
describe('RecordingActivityComponent', () => {
let component: RecordingActivityComponent;
let fixture: ComponentFixture<RecordingActivityComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ RecordingActivityComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(RecordingActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, OnDestroy, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subscription } from 'rxjs';
import {
RecordingDeleteRequestedEvent,
RecordingDownloadClickedEvent,
@ -16,7 +16,6 @@ import { RecordingService } from '../../../../services/recording/recording.servi
import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
import { ILogger } from '../../../../models/logger.model';
import { LoggerService } from '../../../../services/logger/logger.service';
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
/**
* The **RecordingActivityComponent** is the component that allows showing the recording activity.
@ -25,14 +24,13 @@ import { OpenViduComponentsConfigService } from '../../../../services/config/dir
selector: 'ov-recording-activity',
templateUrl: './recording-activity.component.html',
styleUrls: ['./recording-activity.component.scss', '../activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
// TODO: Allow to add more than one recording type
// TODO: Allow to choose where the recording is stored (s3, google cloud, etc)
// TODO: Allow to choose the layout of the recording
export class RecordingActivityComponent implements OnInit, OnDestroy {
export class RecordingActivityComponent implements OnInit {
/**
* @internal
*/
@ -68,20 +66,6 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
/**
* @internal
* Provides event notifications that fire when view recordings button has been clicked.
* This event is triggered when the user wants to view all recordings in an external page.
*/
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
/**
* @internal
* This event is fired when the user clicks on the view recording button.
* It provides the recording ID as event data.
*/
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
/**
* @internal
*/
@ -114,53 +98,12 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/
recordingError: any;
/**
* @internal
*/
hasRoomTracksPublished: boolean = false;
/**
* @internal
*/
mouseHovering: boolean = false;
/**
* @internal
*/
isReadOnlyMode: boolean = false;
/**
* @internal
*/
viewButtonText: string = 'PANEL.RECORDING.VIEW';
/**
* @internal
*/
showStartStopRecordingButton: boolean = true;
/**
* @internal
*/
showViewRecordingsButton: boolean = false;
/**
* @internal
*/
showRecordingList: boolean = true; // Controls visibility of the recording list in the panel
/**
* @internal
*/
showControls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean } = {
play: true,
download: true,
delete: true,
externalView: false
};
private log: ILogger;
private destroy$ = new Subject<void>();
private recordingStatusSubscription: Subscription;
/**
* @internal
@ -171,8 +114,7 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
private actionService: ActionService,
private openviduService: OpenViduService,
private cd: ChangeDetectorRef,
private loggerSrv: LoggerService,
private libService: OpenViduComponentsConfigService
private loggerSrv: LoggerService
) {
this.log = this.loggerSrv.get('RecordingActivityComponent');
}
@ -182,23 +124,13 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/
ngOnInit(): void {
this.subscribeToRecordingStatus();
this.subscribeToTracksChanges();
this.subscribeToConfigChanges();
}
/**
* @internal
*/
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
/**
* @internal
*/
trackByRecordingId(index: number, recording: RecordingInfo): string | undefined {
return recording.id;
if (this.recordingStatusSubscription) this.recordingStatusSubscription.unsubscribe();
}
/**
@ -293,105 +225,11 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
this.recordingService.playRecording(recording);
}
/**
* @internal
*/
viewRecording(recording: RecordingInfo) {
// This method can be overridden or emit a custom event for navigation
// For now, it uses the same behavior as play, but can be customized
if (!recording.filename) {
this.log.e('Error viewing recording. Recording filename is undefined');
return;
}
const payload: RecordingPlayClickedEvent = {
roomName: this.openviduService.getRoomName(),
recordingId: recording.id
};
this.onRecordingPlayClicked.emit(payload);
// You can customize this to navigate to a different page instead
this.recordingService.playRecording(recording);
}
/**
* @internal
*/
viewAllRecordings() {
this.onViewRecordingsClicked.emit();
}
/**
* @internal
* Format duration in seconds to a readable format (e.g., "2m 30s")
*/
formatDuration(seconds: number): string {
if (!seconds || seconds < 0) return '0s';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
} else if (minutes > 0) {
return `${minutes}m ${remainingSeconds}s`;
} else {
return `${remainingSeconds}s`;
}
}
/**
* @internal
* Format file size in bytes to a readable format (e.g., "2.5 MB")
*/
formatFileSize(bytes: number): string {
if (!bytes || bytes < 0) return '0 B';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const size = bytes / Math.pow(1024, i);
return `${size.toFixed(1)} ${sizes[i]}`;
}
private subscribeToConfigChanges() {
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
this.isReadOnlyMode = readOnly;
this.cd.markForCheck();
});
this.libService.recordingActivityShowControls$
.pipe(takeUntil(this.destroy$))
.subscribe((controls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) => {
this.showControls = controls;
this.cd.markForCheck();
});
this.libService.recordingActivityStartStopRecordingButton$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
this.showStartStopRecordingButton = show;
this.cd.markForCheck();
});
this.libService.recordingActivityViewRecordingsButton$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
this.showViewRecordingsButton = show;
this.cd.markForCheck();
});
this.libService.recordingActivityShowRecordingsList$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
this.showRecordingList = show;
this.cd.markForCheck();
});
}
private subscribeToRecordingStatus() {
this.recordingService.recordingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: RecordingStatusInfo) => {
this.recordingStatusSubscription = this.recordingService.recordingStatusObs.subscribe((event: RecordingStatusInfo) => {
const { status, recordingList, error } = event;
this.recordingStatus = status;
if (this.showRecordingList) {
this.recordingList = recordingList;
} else {
// Avoid showing recordings
this.recordingList = [];
}
this.recordingError = error;
this.recordingAlive = this.recordingStatus === RecordingStatus.STARTED;
if (this.recordingStatus !== RecordingStatus.FAILED) {
@ -400,24 +238,4 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
this.cd.markForCheck();
});
}
private subscribeToTracksChanges() {
this.hasRoomTracksPublished = this.openviduService.hasRoomTracksPublished();
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe(() => {
const newValue = this.openviduService.hasRoomTracksPublished();
if (this.hasRoomTracksPublished !== newValue) {
this.hasRoomTracksPublished = newValue;
this.cd.markForCheck();
}
});
this.participantService.remoteParticipants$.pipe(takeUntil(this.destroy$)).subscribe(() => {
const newValue = this.openviduService.hasRoomTracksPublished();
if (this.hasRoomTracksPublished !== newValue) {
this.hasRoomTracksPublished = newValue;
this.cd.markForCheck();
}
});
}
}

View File

@ -8,7 +8,7 @@
<div class="effects-container" fxFlex="100%" fxLayoutAlign="space-evenly none">
<div>
<h4 class="background-title">{{ 'PANEL.BACKGROUND.BLURRED_SECTION' | translate }}</h4>
<h4>{{ 'PANEL.BACKGROUND.BLURRED_SECTION' | translate }}</h4>
<div>
<button
*ngFor="let effect of noEffectAndBlurredBackground"
@ -29,13 +29,12 @@
</div>
<hr />
<div>
<h4 class="background-title">{{ 'PANEL.BACKGROUND.IMAGES_SECTION' | translate }}</h4>
<h4>{{ 'PANEL.BACKGROUND.IMAGES_SECTION' | translate }}</h4>
<div class="grid">
<div
*ngFor="let effect of backgroundImages"
class="effect-button"
[id]="'effect-' + effect.id"
[class.active-effect-btn]="backgroundSelectedId === effect.id"
(click)="applyBackground(effect)"
>

View File

@ -1,6 +1,3 @@
.background-title {
color: var(--ov-text-surface-color);
}
.effects-container {
display: block !important;
overflow-y: auto;
@ -10,9 +7,8 @@
.effect-button {
margin: 5px;
border-radius: var(--ov-surface-radius);
background-color: var(--ov-secondary-action-color);
color: var(--ov-primary-action-color);
border-radius: var(--ov-panel-radius);
background-color: var(--ov-light-color);
width: 60px;
height: 60px;
line-height: inherit;
@ -23,7 +19,7 @@
}
.active-effect-btn {
border: 2px solid var(--ov-accent-action-color);
border: 2px solid var(--ov-tertiary-color);
}
#hard_blur-btn .mat-icon {
@ -38,7 +34,7 @@
.grid img {
max-width: 100%;
max-height: 100%;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
}
/* TODO(mdc-migration): The following rule targets internal classes of slider that may no longer apply for the MDC version. */

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BackgroundEffectsPanelComponent } from './background-effects-panel.component';
describe('BackgroundEffectsPanelComponent', () => {
let component: BackgroundEffectsPanelComponent;
let fixture: ComponentFixture<BackgroundEffectsPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ BackgroundEffectsPanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(BackgroundEffectsPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -12,8 +12,7 @@ import { VirtualBackgroundService } from '../../../services/virtual-background/v
selector: 'ov-background-effects-panel',
templateUrl: './background-effects-panel.component.html',
styleUrls: ['../panel.component.scss', './background-effects-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BackgroundEffectsPanelComponent implements OnInit {
backgroundSelectedId: string;
@ -57,6 +56,10 @@ export class BackgroundEffectsPanelComponent implements OnInit {
}
async applyBackground(effect: BackgroundEffect) {
if (effect.type === EffectType.NONE) {
await this.backgroundService.removeBackground();
} else {
await this.backgroundService.applyBackground(effect);
}
}
}

View File

@ -17,7 +17,7 @@
<p *ngIf="data.isLocal">{{ 'PANEL.CHAT.YOU' | translate }}</p>
<p *ngIf="!data.isLocal">{{ data.participantName }}</p>
</div>
<div class="chat-message">
<div class="msg-content">
<p [innerHTML]="data.message | linkify"></p>
</div>
</div>

View File

@ -1,7 +1,6 @@
$ov-selection-color: #d4d6d7;
.text-container {
color: var(--ov-text-primary-color);
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color);
text-align: center;
font-size: 12px;
flex: inherit;
@ -9,7 +8,6 @@ $ov-selection-color: #d4d6d7;
.text-info {
margin: auto;
color: var(--ov-text-surface-color);
}
.vertical-align {
@ -29,15 +27,13 @@ $ov-selection-color: #d4d6d7;
.input-container {
height: 65px;
display: flex;
background-color: var(--ov-surface-color);
border: 1px solid $ov-selection-color;
background-color: var(--ov-light-color);
padding: 10px 5px 10px 10px;
margin: 10px;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
order: 3;
justify-content: space-evenly;
align-items: none;
}
.input-container textarea {
@ -58,7 +54,6 @@ $ov-selection-color: #d4d6d7;
-moz-box-shadow: none;
box-shadow: none;
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
color: var(--ov-text-surface-color);
}
.message {
@ -78,27 +73,25 @@ $ov-selection-color: #d4d6d7;
.participant-name-container p {
font-size: 13px;
font-style: italic;
color: var(--ov-text-surface-color);
padding: 5px;
font-weight: bold;
color: var(--ov-panel-text-color);
}
.chat-message {
.msg-content {
position: relative;
border-radius: var(--ov-surface-radius);
border-radius: var(--ov-panel-radius);
padding: 8px;
color: var(--ov-secondary-action-color);
color: #000000;
width: auto;
max-width: 95%;
font-size: 14px;
font-size: 13px;
word-break: break-all;
background-color: var(--ov-primary-action-color);
}
#send-btn {
border-radius: var(--ov-surface-radius);
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color);
border-radius: var(--ov-panel-radius);
color: var(--ov-light-color);
background-color: var(--ov-tertiary-color);
align-self: center;
height: 75px;
}
@ -109,7 +102,7 @@ $ov-selection-color: #d4d6d7;
text-align: left;
}
.message.left .msg-detail .chat-message {
.message.left .msg-detail .msg-content {
float: left;
}
@ -121,10 +114,10 @@ $ov-selection-color: #d4d6d7;
text-align: right;
}
.message.right .msg-detail .chat-message {
.message.right .msg-detail .msg-content {
float: right;
}
::ng-deep a:-webkit-any-link {
color: var(--ov-accent-action-color);
color: var(--ov-tertiary-color);
}

View File

@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChatService } from '../../../services/chat/chat.service';
import { ChatServiceMock } from '../../../services/chat/chat.service.mock';
import { ChatPanelComponent } from './chat-panel.component';
describe('ChatPanelComponent', () => {
let component: ChatPanelComponent;
let fixture: ComponentFixture<ChatPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ChatPanelComponent],
providers: [{ provide: ChatService, useClass: ChatServiceMock }]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ChatPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,5 +1,5 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';
import { Subscription } from 'rxjs';
import { ChatMessage } from '../../../models/chat.model';
import { PanelType } from '../../../models/panel.model';
import { ChatService } from '../../../services/chat/chat.service';
@ -13,8 +13,7 @@ import { PanelService } from '../../../services/panel/panel.service';
selector: 'ov-chat-panel',
templateUrl: './chat-panel.component.html',
styleUrls: ['../panel.component.scss', './chat-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChatPanelComponent implements OnInit, AfterViewInit {
/**
@ -34,7 +33,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
*/
messageList: ChatMessage[] = [];
private destroy$ = new Subject<void>();
private chatMessageSubscription: Subscription;
/**
* @ignore
@ -66,8 +65,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
* @ignore
*/
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
if (this.chatMessageSubscription) this.chatMessageSubscription.unsubscribe();
}
/**
@ -110,7 +108,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
}
private subscribeToMessages() {
this.chatService.messagesObs.pipe(takeUntil(this.destroy$)).subscribe((messages: ChatMessage[]) => {
this.chatMessageSubscription = this.chatService.messagesObs.subscribe((messages: ChatMessage[]) => {
this.messageList = messages;
if (this.panelService.isChatPanelOpened()) {
this.scrollToBottom();

View File

@ -1,7 +1,7 @@
.panel-container {
margin: 20px;
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
display: flex;
@ -16,11 +16,6 @@
align-items: center;
}
.panel-title,
.panel-close-button {
color: var(--ov-text-surface-color);
}
.panel-title {
margin-left: 5px;
margin-top: auto;
@ -29,7 +24,7 @@
.panel-close-button {
margin-left: auto;
border-radius: 50%;
border-radius: var(--ov-buttons-radius);
}
::-webkit-scrollbar {
@ -46,6 +41,6 @@
}
::-webkit-scrollbar-track {
background: var(--ov-secondary-action-color);
background: var(--ov-light-color);
border-radius: 4px;
}

View File

@ -8,7 +8,7 @@ import {
Output,
TemplateRef
} from '@angular/core';
import { skip, Subject, takeUntil } from 'rxjs';
import { skip, Subscription } from 'rxjs';
import {
ActivitiesPanelDirective,
AdditionalPanelsDirective,
@ -25,7 +25,6 @@ import {
} from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service';
import { BackgroundEffect } from '../../models/background-effect.model';
import { TemplateManagerService, PanelTemplateConfiguration } from '../../services/template/template-manager.service';
/**
*
@ -38,8 +37,7 @@ import { TemplateManagerService, PanelTemplateConfiguration } from '../../servic
selector: 'ov-panel',
templateUrl: './panel.component.html',
styleUrls: ['./panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PanelComponent implements OnInit {
/**
@ -76,20 +74,42 @@ export class PanelComponent implements OnInit {
*/
@ContentChild(ParticipantsPanelDirective)
set externalParticipantPanel(externalParticipantsPanel: ParticipantsPanelDirective) {
this._externalParticipantPanel = externalParticipantsPanel;
// This directive will has value only when PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalParticipantsPanel) {
this.updateTemplatesAndMarkForCheck();
this.participantsPanelTemplate = externalParticipantsPanel.template;
}
}
// TODO: backgroundEffectsPanel does not provides customization
// @ContentChild(BackgroundEffectsPanelDirective)
// set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) {
// This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
// if (externalBackgroundEffectsPanel) {
// this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template;
// }
// }
// TODO: settingsPanel does not provides customization
// @ContentChild(SettingsPanelDirective)
// set externalSettingsPanel(externalSettingsPanel: SettingsPanelDirective) {
// This directive will has value only when SETTINGS PANEL component tagged with '*ovSettingsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
// if (externalSettingsPanel) {
// this.settingsPanelTemplate = externalSettingsPanel.template;
// }
// }
/**
* @ignore
*/
@ContentChild(ActivitiesPanelDirective)
set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) {
this._externalActivitiesPanel = externalActivitiesPanel;
// This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalActivitiesPanel) {
this.updateTemplatesAndMarkForCheck();
this.activitiesPanelTemplate = externalActivitiesPanel.template;
}
}
@ -98,9 +118,10 @@ export class PanelComponent implements OnInit {
*/
@ContentChild(ChatPanelDirective)
set externalChatPanel(externalChatPanel: ChatPanelDirective) {
this._externalChatPanel = externalChatPanel;
// This directive will has value only when CHAT PANEL component tagged with '*ovChatPanel'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalChatPanel) {
this.updateTemplatesAndMarkForCheck();
this.chatPanelTemplate = externalChatPanel.template;
}
}
@ -109,9 +130,10 @@ export class PanelComponent implements OnInit {
*/
@ContentChild(AdditionalPanelsDirective)
set externalAdditionalPanels(externalAdditionalPanels: AdditionalPanelsDirective) {
this._externalAdditionalPanels = externalAdditionalPanels;
// This directive will has value only when ADDITIONAL PANELS component tagged with '*ovPanelAdditionalPanels'
// is inside of the PANEL component tagged with '*ovPanel'
if (externalAdditionalPanels) {
this.updateTemplatesAndMarkForCheck();
this.additionalPanelsTemplate = externalAdditionalPanels.template;
}
}
@ -172,20 +194,7 @@ export class PanelComponent implements OnInit {
* @internal
*/
isExternalPanelOpened: boolean;
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: PanelTemplateConfiguration = {};
// Store directive references for template setup
private _externalParticipantPanel?: ParticipantsPanelDirective;
private _externalChatPanel?: ChatPanelDirective;
private _externalActivitiesPanel?: ActivitiesPanelDirective;
private _externalAdditionalPanels?: AdditionalPanelsDirective;
private destroy$ = new Subject<void>();
private panelSubscription: Subscription;
private panelEmitersHandler: Map<
PanelType,
@ -197,78 +206,30 @@ export class PanelComponent implements OnInit {
*/
constructor(
private panelService: PanelService,
private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
private cd: ChangeDetectorRef
) {}
/**
* @ignore
*/
ngOnInit(): void {
this.setupTemplates();
this.subscribeToPanelToggling();
this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.ACTIVITIES, this.onActivitiesPanelStatusChanged);
}
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupPanelTemplates(
this._externalParticipantPanel,
this._externalChatPanel,
this._externalActivitiesPanel,
this._externalAdditionalPanels
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.participantsPanelTemplate) {
this.participantsPanelTemplate = this.templateConfig.participantsPanelTemplate;
}
if (this.templateConfig.chatPanelTemplate) {
this.chatPanelTemplate = this.templateConfig.chatPanelTemplate;
}
if (this.templateConfig.activitiesPanelTemplate) {
this.activitiesPanelTemplate = this.templateConfig.activitiesPanelTemplate;
}
if (this.templateConfig.additionalPanelsTemplate) {
this.additionalPanelsTemplate = this.templateConfig.additionalPanelsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
/**
* @ignore
*/
ngOnDestroy() {
this.isChatPanelOpened = false;
this.isParticipantsPanelOpened = false;
this.destroy$.next();
this.destroy$.complete();
if (this.panelSubscription) this.panelSubscription.unsubscribe();
}
private subscribeToPanelToggling() {
this.panelService.panelStatusObs.pipe(skip(1), takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
this.panelSubscription = this.panelService.panelStatusObs.pipe(skip(1)).subscribe((ev: PanelStatusInfo) => {
this.isChatPanelOpened = ev.isOpened && ev.panelType === PanelType.CHAT;
this.isParticipantsPanelOpened = ev.isOpened && ev.panelType === PanelType.PARTICIPANTS;
this.isBackgroundEffectsPanelOpened = ev.isOpened && ev.panelType === PanelType.BACKGROUND_EFFECTS;

View File

@ -1,71 +1,33 @@
<mat-list>
<mat-list-item>
<!-- Main participant container with improved structure -->
<div class="participant-container" [attr.data-participant-id]="_participant?.sid">
<!-- Avatar section with dynamic color -->
<div
class="participant-avatar"
[style.background-color]="_participant?.colorProfile"
[attr.aria-label]="'Avatar for ' + participantDisplayName"
>
<div matListItemIcon class="participant-avatar" [style.background-color]="_participant.colorProfile">
<mat-icon>person</mat-icon>
</div>
<h3 matListItemTitle class="participant-name">{{ _participant.name }}
<span *ngIf="_participant.isLocal"> ({{ 'PANEL.PARTICIPANTS.YOU' | translate }})</span>
</h3>
<p matListItemLine class="participant-subtitle">{{ _participant | tracksPublishedTypes }}</p>
<!-- <p matListItemLine>
<span class="participant-subtitle"></span>
</p> -->
<!-- Content section with name and status -->
<div class="participant-content">
<div class="participant-name">
{{ participantDisplayName }}
<span *ngIf="isLocalParticipant" class="local-indicator">
{{ 'PANEL.PARTICIPANTS.YOU' | translate }}
</span>
<!-- Participant badges -->
<div class="participant-badges">
<ng-container *ngTemplateOutlet="participantBadgeTemplate"></ng-container>
</div>
</div>
<div class="participant-subtitle">
<span class="status-indicator">
{{ _participant | tracksPublishedTypes }}
</span>
<!-- Additional status indicators -->
<span *ngIf="_participant?.isMutedForcibly" class="status-indicator">
<mat-icon>volume_off</mat-icon>
{{ 'PANEL.PARTICIPANTS.MUTED' | translate }}
</span>
</div>
</div>
<!-- Action buttons section -->
<div class="participant-action-buttons">
<!-- Mute/Unmute button for remote participants -->
<div class="participant-action-buttons" matListItemMeta>
<button
mat-icon-button
id="mute-btn"
*ngIf="!isLocalParticipant && showMuteButton"
[class.warn-btn]="_participant?.isMutedForcibly"
*ngIf="!_participant.isLocal && showMuteButton"
[class.warn-btn]="_participant.isMutedForcibly"
(click)="toggleMuteForcibly()"
[disabled]="!_participant"
[disableRipple]="true"
[attr.aria-label]="
_participant?.isMutedForcibly
? ('PANEL.PARTICIPANTS.UNMUTE' | translate) + ' ' + participantDisplayName
: ('PANEL.PARTICIPANTS.MUTE' | translate) + ' ' + participantDisplayName
"
[matTooltip]="
_participant?.isMutedForcibly ? ('PANEL.PARTICIPANTS.UNMUTE' | translate) : ('PANEL.PARTICIPANTS.MUTE' | translate)
"
>
<mat-icon *ngIf="!_participant?.isMutedForcibly">volume_up</mat-icon>
<mat-icon *ngIf="_participant?.isMutedForcibly">volume_off</mat-icon>
<mat-icon *ngIf="!_participant.isMutedForcibly">volume_up</mat-icon>
<mat-icon *ngIf="_participant.isMutedForcibly">volume_off</mat-icon>
</button>
<!-- External item elements with improved structure -->
<div class="external-elements" *ngIf="hasExternalElements">
<!-- External item elements -->
<ng-container *ngIf="participantPanelItemElementsTemplate">
<ng-container *ngTemplateOutlet="participantPanelItemElementsTemplate"></ng-container>
</div>
</div>
</ng-container>
</div>
</mat-list-item>
</mat-list>

View File

@ -1,443 +1,59 @@
:host {
// Container for the participant item
.participant-container {
position: relative;
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: var(--ov-surface-radius, 8px);
background-color: var(--ov-surface-background, #ffffff);
border-bottom: 1px solid var(--ov-surface-border, #e0e0e0);
transition: all 0.2s ease-in-out;
min-height: 64px;
// &:hover {
// background-color: var(--ov-surface-hover, #f5f5f5);
// transform: translateY(-1px);
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
// }
&:last-child {
border-bottom: none;
}
// Loading state
&.loading {
opacity: 0.7;
pointer-events: none;
&::after {
content: '';
position: absolute;
top: 50%;
right: 16px;
width: 16px;
height: 16px;
border: 2px solid var(--ov-primary-color, #1976d2);
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
}
// Focus state for keyboard navigation
&:focus-within {
outline: 2px solid var(--ov-primary-color, #1976d2);
outline-offset: 2px;
}
}
// Avatar styling with improved design
.participant-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: var(--ov-surface-radius);
margin-right: 12px;
padding: 0;
color: #ffffff;
font-weight: 500;
flex-shrink: 0;
position: relative;
overflow: hidden;
mat-icon {
font-size: 20px;
width: 20px;
height: 20px;
z-index: 1;
}
display: inherit;
border-radius: var(--ov-panel-radius);
margin: auto !important;
padding: 10px;
color: var(--ov-panel-text-color);
}
// Main content area
.participant-content {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0; // Allows text truncation
margin-right: 8px;
}
// Participant name styling
.participant-name {
font-weight: 600 !important;
font-size: 14px;
line-height: 1.2;
color: var(--ov-text-primary, #212121);
margin: 0 0 4px 0;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
// Local participant indicator
.local-indicator {
font-size: 10px;
font-weight: 600;
color: var(--ov-primary-color, #1976d2);
background-color: var(--ov-primary-light, #e3f2fd);
padding: 4px 8px;
border-radius: var(--ov-surface-radius);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
border: 1px solid var(--ov-primary-color, #1976d2);
}
}
// Subtitle styling
.participant-subtitle {
font-style: normal;
font-size: 12px !important;
font-weight: 400;
font-style: italic;
font-size: 11px !important;
margin: 0;
color: var(--ov-text-secondary, #757575);
line-height: 1.3;
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
// Status indicators
.status-indicator {
display: inline-flex;
align-items: center;
gap: 3px;
mat-icon {
font-size: 12px;
width: 12px;
height: 12px;
}
.participant-name {
font-weight: bold !important;
color: var(--ov-panel-text-color);
}
// Different colors for different statuses
&.camera-on {
color: var(--ov-success-color, #4caf50);
}
&.camera-off {
color: var(--ov-warning-color, #ff9800);
}
&.microphone-muted {
color: var(--ov-error-color, #d32f2f);
}
}
}
// Action buttons container
.participant-action-buttons {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
margin-left: auto;
}
// Mute button styling
#mute-btn {
width: 32px;
height: 32px;
border-radius: 50%;
color: var(--ov-text-secondary, #757575);
background-color: transparent;
transition: all 0.2s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&:hover {
background-color: var(--ov-surface-hover, #f5f5f5);
color: var(--ov-text-primary, #212121);
transform: scale(1.1);
::ng-deep .participant-action-buttons > *:not(#mute-btn) {
display: contents;
}
&:focus {
outline: 2px solid var(--ov-primary-color, #1976d2);
outline-offset: 2px;
}
&:disabled {
opacity: 0.5;
pointer-events: none;
}
&.warn-btn {
color: var(--ov-error-color, #d32f2f);
background-color: var(--ov-error-light, #ffebee);
&:hover {
background-color: var(--ov-error-color, #d32f2f);
color: #ffffff;
}
// Pulsing animation for muted state
animation: pulse 2s infinite;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
}
// Participant badges container
.participant-badges {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
// Badge styling
::ng-deep .badge {
// Badge variants
&.moderator {
color: var(--ov-warning-color, #f57c00);
}
&.speaker {
color: var(--ov-primary-color, #1976d2);
}
&.host {
color: var(--ov-success-color, #4caf50);
}
}
}
// After local participant content area
.after-local-content {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--ov-surface-border, #e0e0e0);
animation: fadeIn 0.3s ease-in-out;
background-color: var(--ov-surface-alt, #fafafa);
border-radius: var(--ov-surface-radius, 8px);
padding: 12px;
}
// External item elements styling
.external-elements {
display: flex;
align-items: center;
gap: 4px;
// Custom styling for external buttons
::ng-deep button {
transition: all 0.2s ease-in-out;
&:hover {
transform: scale(1.05);
}
}
}
// Material Design overrides for better integration
mat-list {
padding: 0;
::ng-deep .participant-action-buttons > *:not(#mute-btn) > * {
margin: auto;
}
::ng-deep .mat-mdc-list-item {
height: auto !important;
padding: 0 !important;
min-height: auto !important;
border-radius: var(--ov-surface-radius, 8px);
height: max-content !important;
padding-bottom: 10px !important;
}
mat-list {
padding: 3px;
}
::ng-deep .mdc-list-item__content {
padding: 0 !important;
align-self: stretch !important;
width: 100%;
padding-left: 10px !important;
align-self: center !important;
}
::ng-deep .mat-mdc-list-base {
--mdc-list-list-item-hover-label-text-color: unset;
--mdc-list-list-item-hover-leading-icon-color: unset;
padding: 0;
}
::ng-deep .mat-mdc-list-item:hover {
background-color: transparent !important;
}
// Animations
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
// Responsive design
@media (max-width: 768px) {
.participant-container {
padding: 10px 12px;
min-height: 56px;
}
.participant-avatar {
width: 36px;
height: 36px;
margin-right: 10px;
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
&::after {
width: 10px;
height: 10px;
bottom: 1px;
right: 1px;
}
}
.participant-name {
font-size: 13px;
.local-indicator {
font-size: 9px;
padding: 2px 6px;
}
}
.participant-subtitle {
font-size: 11px !important;
}
#mute-btn {
width: 28px;
height: 28px;
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
}
border-radius: var(--ov-buttons-radius);
}
.after-local-content {
margin-top: 10px;
padding-top: 10px;
padding: 10px;
}
}
// High contrast mode support
@media (prefers-contrast: high) {
.participant-container {
border: 2px solid var(--ov-text-primary, #212121);
}
.participant-avatar {
border: 2px solid var(--ov-surface-background, #ffffff);
}
.local-indicator {
border-width: 2px;
}
}
// Reduced motion support
@media (prefers-reduced-motion: reduce) {
.participant-container,
.participant-avatar,
#mute-btn,
.after-local-content,
.external-elements ::ng-deep button {
transition: none;
animation: none;
}
.participant-container:hover {
transform: none;
}
.participant-avatar:hover,
#mute-btn:hover,
.external-elements ::ng-deep button:hover {
transform: none;
}
#mute-btn.warn-btn {
animation: none;
}
}
// Dark theme support
@media (prefers-color-scheme: dark) {
.participant-container {
background-color: var(--ov-surface-background, #424242);
border-bottom-color: var(--ov-surface-border, #616161);
&:hover {
background-color: var(--ov-surface-hover, #484848);
}
}
.participant-name {
color: var(--ov-text-primary, #ffffff);
}
.participant-subtitle {
color: var(--ov-text-secondary, #cccccc);
}
.after-local-content {
background-color: var(--ov-surface-alt, #373737);
}
.warn-btn {
/* background-color: var(--ov-warn-color) !important; */
color: var(--ov-warn-color);
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ParticipantPanelItemComponent } from './participant-panel-item.component';
describe('ParticipantPanelItemComponent', () => {
let component: ParticipantPanelItemComponent;
let fixture: ComponentFixture<ParticipantPanelItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ParticipantPanelItemComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ParticipantPanelItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,23 +1,21 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-components-angular.directive';
import { ParticipantPanelParticipantBadgeDirective } from '../../../../directives/template/internals.directive';
import { ParticipantModel } from '../../../../models/participant.model';
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
import { OpenViduComponentsConfigService } from '../../../../services/config/openvidu-components-angular.config.service';
import { ParticipantService } from '../../../../services/participant/participant.service';
import { TemplateManagerService, ParticipantPanelItemTemplateConfiguration } from '../../../../services/template/template-manager.service';
/**
*
* The **ParticipantPanelItemComponent** is hosted inside of the {@link ParticipantsPanelComponent}.
* It displays participant information with enhanced UI/UX, including support for custom content
* injection through structural directives.
* It is in charge of displaying the participants information inside of the ParticipansPanelComponent.
*/
@Component({
selector: 'ov-participant-panel-item',
templateUrl: './participant-panel-item.component.html',
styleUrls: ['./participant-panel-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
/**
@ -36,69 +34,40 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
*/
@ContentChild(ParticipantPanelItemElementsDirective)
set externalItemElements(externalItemElements: ParticipantPanelItemElementsDirective) {
this._externalItemElements = externalItemElements;
// This directive will has value only when ITEM ELEMENTS component tagget with '*ovParticipantPanelItemElements' directive
// is inside of the P PANEL ITEM component tagged with '*ovParticipantPanelItem' directive
if (externalItemElements) {
this.updateTemplatesAndMarkForCheck();
this.participantPanelItemElementsTemplate = externalItemElements.template;
}
}
/**
* The participant to be displayed
* @ignore
*/
@Input()
set participant(participant: ParticipantModel) {
this._participant = participant;
}
/**
* @ignore
*/
@ContentChild(ParticipantPanelParticipantBadgeDirective)
set externalParticipantBadge(participantBadge: ParticipantPanelParticipantBadgeDirective) {
this._externalParticipantBadge = participantBadge;
if (participantBadge) {
this.updateTemplatesAndMarkForCheck();
}
}
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: ParticipantPanelItemTemplateConfiguration = {};
// Store directive references for template setup
private _externalItemElements?: ParticipantPanelItemElementsDirective;
private _externalParticipantBadge?: ParticipantPanelParticipantBadgeDirective;
/**
* The participant to be displayed
*/
@Input()
set participant(participant: ParticipantModel) {
this._participant = participant;
this.cd.markForCheck();
}
/**
* @internal
* Current participant being displayed
*/
_participant: ParticipantModel;
/**
* Whether to show the mute button for remote participants
*/
@Input()
muteButton: boolean = true;
/**
* @ignore
*/
constructor(
private libService: OpenViduComponentsConfigService,
private participantService: ParticipantService,
private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
private cd: ChangeDetectorRef
) {}
/**
* @ignore
*/
ngOnInit(): void {
this.setupTemplates();
this.subscribeToParticipantPanelItemDirectives();
}
@ -110,72 +79,14 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
}
/**
* Toggles the mute state of a remote participant
* @ignore
*/
toggleMuteForcibly() {
if (this._participant && !this._participant.isLocal) {
if (this._participant) {
this.participantService.setRemoteMutedForcibly(this._participant.sid, !this._participant.isMutedForcibly);
}
}
/**
* Gets the template for local participant badge
*/
get participantBadgeTemplate(): TemplateRef<any> | undefined {
return this._externalParticipantBadge?.template;
}
/**
* Checks if the current participant is the local participant
*/
get isLocalParticipant(): boolean {
return this._participant?.isLocal || false;
}
/**
* Gets the participant's display name
*/
get participantDisplayName(): string {
return this._participant?.name || '';
}
/**
* Checks if external elements are available
*/
get hasExternalElements(): boolean {
return !!this.participantPanelItemElementsTemplate;
}
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupParticipantPanelItemTemplates(this._externalItemElements);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.participantPanelItemElementsTemplate) {
this.participantPanelItemElementsTemplate = this.templateConfig.participantPanelItemElementsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
private subscribeToParticipantPanelItemDirectives() {
this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => {
this.showMuteButton = value;

View File

@ -7,13 +7,14 @@
</div>
<div class="scrollable">
<div class="local-participant-container" *ngIf="localParticipant">
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: localParticipant }"></ng-container>
<mat-divider *ngIf="true"></mat-divider>
</div>
<ng-container *ngTemplateOutlet="participantPanelAfterLocalParticipantTemplate"></ng-container>
<div class="remote-participants-container" id="remote-participants-container" *ngIf="remoteParticipants.length > 0">
<div *ngFor="let participant of this.remoteParticipants" id="remote-participant-item">
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: participant }"></ng-container>
</div>

Some files were not shown because too many files have changed in this diff Show More