diff --git a/openvidu-server/docker/openvidu-deployment-tester/Dockerfile b/openvidu-server/docker/openvidu-deployment-tester/Dockerfile index 1c31c67a..42df8288 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/Dockerfile +++ b/openvidu-server/docker/openvidu-deployment-tester/Dockerfile @@ -14,7 +14,8 @@ RUN apt-get update && \ x11-utils \ wget \ python3 \ - python3-pip + python3-pip \ + ffmpeg # Install latest chrome RUN wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \ diff --git a/openvidu-server/docker/openvidu-deployment-tester/requirements.txt b/openvidu-server/docker/openvidu-deployment-tester/requirements.txt index dd593bcf..7e5dae3c 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/requirements.txt +++ b/openvidu-server/docker/openvidu-deployment-tester/requirements.txt @@ -1,4 +1,6 @@ selenium==4.8.2 webdriver-manager==3.8.5 prettytable==3.6.0 -beautifulsoup4==4.12.0 \ No newline at end of file +beautifulsoup4==4.12.0 +opencv-python-headless==4.7.0.72 +numpy==1.24.2 \ No newline at end of file diff --git a/openvidu-server/docker/openvidu-deployment-tester/src/cli_utils.py b/openvidu-server/docker/openvidu-deployment-tester/src/cli_utils.py index ab1159fd..38731f43 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/src/cli_utils.py +++ b/openvidu-server/docker/openvidu-deployment-tester/src/cli_utils.py @@ -9,12 +9,17 @@ def test_parser(subparsers, command): test_parser = subparsers.add_parser(command) test_parser.add_argument("--openvidu-url", required=True, help="OpenVidu URL to test") test_parser.add_argument("--openvidu-secret", required=True, help="OpenVidu secret used for OpenVidu API") + test_parser.add_argument("--browser", required=True, choices=["chrome", "firefox"], help="Browser to test") + test_parser.add_argument("--turn", required=False, default=False, action='store_true', help="Force TURN usage. Only available for Firefox") + + # Basic test specific parameter if command == "basic-test": test_parser.add_argument("--openvidu-edition", required=True, choices=["ce", "pro", "enterprise"], help="OpenVidu edition to test") - test_parser.add_argument("--browser", required=True, choices=["chrome", "firefox"], help="Browser to test") - test_parser.add_argument("--turn", required=False, default=False, action='store_true', help="Force TURN usage. Only available for Firefox") - elif command == "recording-test": + if command.startswith("call-test"): test_parser.add_argument("--openvidu-call-url", required=True, help="OpenVidu Call URL to test") + test_parser.add_argument("--number-of-clients", required=False, nargs='?', help="Number of clients to test", const=4, default=4, type=int) + test_parser.add_argument("--openvidu-call-username", required=True, help="OpenVidu Call user to test") + test_parser.add_argument("--openvidu-call-password", required=True, help="OpenVidu Call password to test") def initialize_parser(): parser = argparse.ArgumentParser() @@ -22,13 +27,7 @@ def initialize_parser(): install_drivers_parser(subparsers) test_parser(subparsers, "basic-test") + test_parser(subparsers, "call-test-recording") + test_parser(subparsers, "call-test") - return parser - -def print_args(args): - print("Executing command: " + args.command) - for arg, value in vars(args).items(): - if arg != 'command': - if arg == 'openvidu-secret': - value = '********' - print(f"\t{arg}: {value}") \ No newline at end of file + return parser \ No newline at end of file diff --git a/openvidu-server/docker/openvidu-deployment-tester/src/main.py b/openvidu-server/docker/openvidu-deployment-tester/src/main.py index 253e6a8f..9a380a21 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/src/main.py +++ b/openvidu-server/docker/openvidu-deployment-tester/src/main.py @@ -5,7 +5,6 @@ import tests # Define your commands here def install_drivers(args): - cli_utils.print_args(args) utils.install_drivers(args.chrome_version, args.gecko_version) # Define the command-line arguments @@ -21,5 +20,9 @@ if args.command == "install-drivers": install_drivers(args) elif args.command == "basic-test": tests.basic_test(args) +elif args.command == "call-test-recording": + tests.call_recording_test(args) +elif args.command == "call-test": + tests.call_test(args) else: print("Error: No command specified") \ No newline at end of file diff --git a/openvidu-server/docker/openvidu-deployment-tester/src/tests.py b/openvidu-server/docker/openvidu-deployment-tester/src/tests.py index 60e0afb9..d470a05d 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/src/tests.py +++ b/openvidu-server/docker/openvidu-deployment-tester/src/tests.py @@ -1,38 +1,26 @@ import utils +import time from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By +from requests.auth import HTTPBasicAuth +import requests def basic_test(args): print("Running basic test with args:", args) driver = utils.runBrowser(args.browser, args.turn) + base_url = args.openvidu_url + if args.openvidu_edition == "ce": - url_test = args.openvidu_url + '/dashboard' - driver.get(url_test) - - elem = driver.find_element(By.ID,'test-btn') - elem.send_keys(Keys.RETURN) - - elem = driver.find_element(By.NAME, 'secret') - elem.send_keys(args.openvidu_secret) - - elem = driver.find_element(By.ID, 'join-btn') - elem.send_keys(Keys.RETURN) + driver.get(f"{base_url}/dashboard") + driver.find_element(By.ID, 'test-btn').send_keys(Keys.RETURN) + driver.find_element(By.NAME, 'secret').send_keys(args.openvidu_secret) + driver.find_element(By.ID, 'join-btn').send_keys(Keys.RETURN) elif args.openvidu_edition == "pro": - url_test = args.openvidu_url + '/inspector' - driver.get(url_test) - - elem = driver.find_element(By.ID, 'secret-input') - elem.send_keys(args.openvidu_secret) - - elem = driver.find_element(By.ID, 'login-btn') - elem.send_keys(Keys.RETURN) - - # print('data:image/png;base64,' + self.driver.get_screenshot_as_base64()) - elem = driver.find_element(By.ID,'menu-test-btn') - elem.send_keys(Keys.RETURN) - - elem = driver.find_element(By.ID,'test-btn') - elem.send_keys(Keys.RETURN) + driver.get(f"{base_url}/inspector") + driver.find_element(By.ID, 'secret-input').send_keys(args.openvidu_secret) + driver.find_element(By.ID, 'login-btn').send_keys(Keys.RETURN) + driver.find_element(By.ID, 'menu-test-btn').send_keys(Keys.RETURN) + driver.find_element(By.ID, 'test-btn').send_keys(Keys.RETURN) else: print("Error: Invalid OpenVidu edition specified") exit(1) @@ -44,9 +32,75 @@ def basic_test(args): except: video_error = True finally: - # print('data:image/png;base64,' + driver.get_screenshot_as_base64()) if args.browser == "firefox": utils.print_candidates(driver) - if video_error == True: - raise Exception('Error. No video detected') \ No newline at end of file + if video_error: + raise Exception('Error. No video detected') + +def call_recording_test(args): + print("Testing recording with OpenVidu Call with args:", args) + driver = utils.runBrowser(args.browser, args.turn) + driver.get(args.openvidu_call_url) + + utils.call_login(driver, args.openvidu_call_username, args.openvidu_call_password) + session_name = utils.call_session(driver, args.number_of_clients) + + print('Starting recording') + driver.find_element(By.ID, 'activities-panel-btn').send_keys(Keys.RETURN) + time.sleep(5) + driver.find_element(By.ID, 'mat-expansion-panel-header-0').send_keys(Keys.RETURN) + time.sleep(5) + driver.find_element(By.ID, 'start-recording-btn').send_keys(Keys.RETURN) + + print('Recording started') + time.sleep(10) + + print('Stopping recording') + driver.find_element(By.ID, 'stop-recording-btn').send_keys(Keys.RETURN) + print('Recording stopped') + time.sleep(5) + utils.close_all_tabs(driver, args.browser) + + print(f'Downloading recording from {args.openvidu_url}/openvidu/recordings/{session_name}/{session_name}.mp4') + url = f'{args.openvidu_url}/openvidu/recordings/{session_name}/{session_name}.mp4' + + if args.browser == "chrome": + print("Checking if recording is green (Default video color from chrome emulated video is green)") + is_green = utils.is_video_green(url, HTTPBasicAuth('OPENVIDUAPP', args.openvidu_secret), f'{session_name}.mp4') + is_video_valid = utils.is_video_valid(url, HTTPBasicAuth('OPENVIDUAPP', args.openvidu_secret), f'{session_name}.mp4') + print("Recording is", "green" if is_green else "not green") + if not is_green or not is_video_valid: + exit(1) + elif args.browser == "firefox": + # Close all tabs + print("Checking if recording is a valid video") + is_valid = utils.is_video_valid(url, HTTPBasicAuth('OPENVIDUAPP', args.openvidu_secret), f'{session_name}.mp4') + print("Recording is", "valid" if is_valid else "not valid") + if not is_valid: + exit(1) + + print('Removing recording') + url = f'{args.openvidu_url}/openvidu/api/recordings/stop/{session_name}' + requests.delete(url, auth=HTTPBasicAuth('OPENVIDUAPP', args.openvidu_secret), verify=False) + +def call_test(args): + print(f"Testing recording with OpenVidu Call with args: {args}") + driver = utils.runBrowser(args.browser, args.turn) + driver.get(args.openvidu_call_url) + + utils.call_login(driver, args.openvidu_call_username, args.openvidu_call_password) + utils.call_session(driver, args.number_of_clients) + time.sleep(5) + + videos = driver.find_elements(By.TAG_NAME, 'video') + + if len(videos) != args.number_of_clients: + raise Exception('Error. Number of videos is not equal to number of clients') + + for i, video in enumerate(videos): + current_time = float(video.get_attribute('currentTime')) + print(f'Video {i} is playing. Current time: {current_time}') + if current_time < 1: + raise Exception('Error. Video is not playing') + utils.close_all_tabs(driver, args.browser) \ No newline at end of file diff --git a/openvidu-server/docker/openvidu-deployment-tester/src/utils.py b/openvidu-server/docker/openvidu-deployment-tester/src/utils.py index 4a1c2778..54311a77 100644 --- a/openvidu-server/docker/openvidu-deployment-tester/src/utils.py +++ b/openvidu-server/docker/openvidu-deployment-tester/src/utils.py @@ -1,11 +1,16 @@ import time import os +import cv2 +import numpy as np +import requests +import subprocess from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.firefox.service import Service as FirefoxService from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium import webdriver from bs4 import BeautifulSoup from prettytable import from_html_one @@ -46,8 +51,11 @@ def runChrome(): # Load version from file chrome_version = None - with open("chrome_version", "r") as f: - chrome_version = f.read() + + # Check if chrome version file exists and load it + if os.path.isfile("chrome_version"): + with open("chrome_version", "r") as f: + chrome_version = f.read() print("Running chrome...") print("Chrome version: ", chrome_version) @@ -68,8 +76,11 @@ def runFirefox(turn=False): # Load version from file gecko_version = None - with open("gecko_version", "r") as f: - gecko_version = f.read() + + # Check if gecko version file exists and load it + if os.path.isfile("gecko_version"): + with open("gecko_version", "r") as f: + gecko_version = f.read() print("Running firefox with Turn: ", turn) print("Gecko version: ", gecko_version) @@ -120,4 +131,92 @@ def print_candidates(driver): except: print('[Warn] Some candidates may not appear in test result') driver.switch_to.window(driver.window_handles[0]) - driver.close() \ No newline at end of file + driver.close() + +def call_session(driver, num_of_clients): + print('First session entering') + session_name = driver.find_element(By.ID, 'session-name-input').get_attribute('value') + driver.find_element(By.ID, 'join-btn').send_keys(Keys.RETURN) + + print('Joining user 1 to session', session_name) + driver.find_element(By.ID, 'join-button').send_keys(Keys.RETURN) + url_link = driver.current_url + + for i in range(2, num_of_clients + 1): + print(f'Joining user {i} to session {session_name}') + driver.execute_script(f'window.open("{url_link}", "_blank");') + driver.switch_to.window(driver.window_handles[-1]) + driver.find_element(By.ID, 'join-button').send_keys(Keys.RETURN) + time.sleep(5) + + driver.switch_to.window(driver.window_handles[0]) + return session_name + +def call_login(driver, username, password): + print('Logging in') + driver.find_element(By.NAME, 'username').send_keys(username) + driver.find_element(By.NAME, 'password').send_keys(password) + driver.find_element(By.ID, 'join-btn').send_keys(Keys.RETURN) + +def close_all_tabs(driver, browser): + if browser == 'firefox': + # Close all tabs + for i in range(0, len(driver.window_handles)): + driver.switch_to.window(driver.window_handles[0]) + driver.close() + time.sleep(1) + +def is_video_green(url, basic_auth, video_filename): + # Download the video from the URL + response = requests.get(url, auth=basic_auth, verify=False) + open(video_filename, 'wb').write(response.content) + + # Read the video using OpenCV + cap = cv2.VideoCapture(video_filename) + + # Parameters to calculate if the video is mostly green + total_frames = 0 + green_frames = 0 + green_threshold = 0.8 + + while cap.isOpened(): + ret, frame = cap.read() + if not ret: + break + + total_frames += 1 + + # Define green ranges in the RGB color space + lower_green = np.array([0, 100, 0]) + upper_green = np.array([100, 255, 100]) + + # Create a mask to select only green pixels + mask = cv2.inRange(frame, lower_green, upper_green) + + # Calculate the percentage of green pixels in the frame + green_ratio = np.sum(mask == 255) / (mask.shape[0] * mask.shape[1]) + + if green_ratio > green_threshold: + green_frames += 1 + + # Clean up resources + cap.release() + + # Remove the video file + os.remove(video_filename) + + # Determine if the video is mostly green + return green_frames / total_frames > 0.8 + +def is_video_valid(url, basic_auth, video_filename): + response = requests.get(url, auth=basic_auth, verify=False) + open(video_filename, 'wb').write(response.content) + try: + cmd = ["ffprobe", "-v", "error", "-show_streams", "-select_streams", "v:0", video_filename] + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + if output: + return True + except subprocess.CalledProcessError as e: + print(f"Error: {e}") + finally: + os.remove(video_filename) \ No newline at end of file