openvidu-test-browsers: newly updated Driver users

v2
pabloFuente 2025-11-07 20:32:11 +01:00
parent 79e2385358
commit c5ba1f770c
4 changed files with 186 additions and 71 deletions

View File

@ -1,6 +1,7 @@
package io.openvidu.test.browsers;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import io.appium.java_client.android.AndroidDriver;
@ -8,7 +9,7 @@ import io.appium.java_client.android.options.UiAutomator2Options;
public class AndroidAppUser extends BrowserUser {
public AndroidAppUser(String userName, int timeOfWaitInSeconds, String appPath) {
public AndroidAppUser(String userName, int timeOfWaitInSeconds, String appPath, boolean setAutoWebview) {
super(userName, timeOfWaitInSeconds);
String REMOTE_URL = System.getProperty("REMOTE_URL_ANDROID");
@ -17,13 +18,36 @@ public class AndroidAppUser extends BrowserUser {
}
UiAutomator2Options options = new UiAutomator2Options();
options.setAutoWebview(true);
options.setApp(appPath);
options.setAutoGrantPermissions(true);
if (setAutoWebview) {
options.setAutoWebview(true);
options.setCapability("appium:chromeOptions", java.util.Map.of(
"args", java.util.List.of(
// Fake media stream for testing without hardware
"--use-fake-ui-for-media-stream",
"--use-fake-device-for-media-stream",
"--disable-popup-blocking",
// Video rendering in emulator
"--ignore-gpu-blocklist",
"--enable-gpu-rasterization",
"--enable-zero-copy",
"--disable-gpu-driver-bug-workarounds",
// WebRTC optimizations
"--enable-features=WebRTC-H264WithOpenH264FFmpeg",
"--disable-features=WebRtcHideLocalIpsWithMdns",
"--force-webrtc-ip-handling-policy=default",
// General WebView stability
"--disable-dev-shm-usage",
"--no-sandbox",
"--disable-setuid-sandbox",
// Allow autoplay
"--autoplay-policy=no-user-gesture-required")));
}
URL url = null;
try {
url = new URL(REMOTE_URL);
url = URI.create(REMOTE_URL).toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}

View File

@ -33,15 +33,32 @@ public class AndroidChromeUser extends BrowserUser {
}
ChromeOptions options = new ChromeOptions();
options.addArguments("no-first-run", "disable-infobars", "use-fake-ui-for-media-stream",
"use-fake-device-for-media-stream", "ignore-certificate-errors",
"autoplay-policy=no-user-gesture-required");
options.addArguments(
// Media stream fake devices
"use-fake-ui-for-media-stream",
"use-fake-device-for-media-stream",
// Skip Chrome welcome/setup screens
"no-first-run",
"disable-infobars",
"no-default-browser-check",
// Allow self-signed certificates (for test app)
"ignore-certificate-errors",
// Auto-play policy for media
"autoplay-policy=no-user-gesture-required",
// Basic stability
"no-sandbox",
"disable-dev-shm-usage"
);
// Enable W3C protocol
options.setExperimentalOption("w3c", true);
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
capabilities.setCapability(CapabilityType.BROWSER_NAME, "Chrome");
capabilities.setCapability("appium:automationName", "UiAutomator2");
// Grant permissions at Android level instead of Chrome prefs
capabilities.setCapability("appium:autoGrantPermissions", true);
URL url = null;
try {

View File

@ -68,13 +68,18 @@ public class OpenViduTestE2e {
private static class AndroidContainerWaitStrategy extends AbstractWaitStrategy {
@Override
protected void waitUntilReady() {
int[] retryCount = { 0 };
retryUntilSuccess(600, TimeUnit.SECONDS, () -> {
String deviceStatus = this.waitStrategyTarget.execInContainer("bash", "-c", "cat device_status")
.getStdout();
log.info("Device status: {}", deviceStatus);
if (retryCount[0] % 10 == 0) {
log.info("Android container device status: {}", deviceStatus.trim());
}
retryCount[0]++;
if (!"READY".equals(deviceStatus.trim())) {
throw new Exception();
}
log.info("Android container device status: {}", deviceStatus.trim());
return true;
});
}
@ -117,7 +122,6 @@ public class OpenViduTestE2e {
// https://hub.docker.com/r/selenium/standalone-edge/tags
protected static String EDGE_VERSION = "latest";
protected static String OPENVIDU_DEPLOYMENT = "http://localhost:5000/";
protected static String DOCKER_ANDROID_IMAGE = "budtmo/docker-android:latest";
protected static Exception ex = null;
@ -214,12 +218,23 @@ public class OpenViduTestE2e {
}
private static GenericContainer<?> androidContainer(String image, long shmSize) {
GenericContainer<?> android = new GenericContainer<>(DockerImageName.parse(image)).withEnv(Map.of(
"EMULATOR_DEVICE", "Samsung Galaxy S10", "APPIUM", "true", "APPIUM_HOST", "172.17.0.1", "APPIUM_PORT",
"4723", "APPIUM_ADDITIONAL_ARGS",
"--log /var/log/supervisor/appium.log --relaxed-security --allow-cors --allow-insecure=chromedriver_autodownload",
"MOBILE_WEB_TEST", "true", "RELAXED_SECURITY", "true", "WEB_VNC", "true", "WEB_LOG", "false",
"DATAPARTITION", "2500m")).withPrivilegedMode(true).withSharedMemorySize(shmSize)
Map<String, String> envVars = new java.util.HashMap<>();
envVars.put("EMULATOR_DEVICE", "Samsung Galaxy S10");
envVars.put("APPIUM", "true");
envVars.put("APPIUM_HOST", "172.17.0.1");
envVars.put("APPIUM_PORT", "4723");
envVars.put("APPIUM_ADDITIONAL_ARGS",
"--log /var/log/supervisor/appium.log --relaxed-security --allow-cors --allow-insecure=chromedriver_autodownload");
envVars.put("MOBILE_WEB_TEST", "true");
envVars.put("RELAXED_SECURITY", "true");
envVars.put("WEB_VNC", "true");
envVars.put("WEB_LOG", "false");
envVars.put("DATAPARTITION", "8192m");
envVars.put("EMULATOR_ARGS", "-gpu swiftshader_indirect -no-snapshot -no-audio -memory 8192 -partition-size 8192");
GenericContainer<?> android = new GenericContainer<>(DockerImageName.parse(image))
.withEnv(envVars)
.withPrivilegedMode(true).withSharedMemorySize(shmSize)
.withExposedPorts(6080, 5554, 5555, 4723).withFileSystemBind("/dev/kvm", "/dev/kvm")
.withFileSystemBind("/opt/openvidu/android", "/opt/openvidu/android").withReuse(true)
.waitingFor(new AndroidContainerWaitStrategy());
@ -369,12 +384,6 @@ public class OpenViduTestE2e {
DOCKER_ANDROID_IMAGE = dockerAndroidImage;
}
log.info("Using Docker Android image {}", DOCKER_ANDROID_IMAGE);
String openviduDeployment = System.getProperty("OPENVIDU_DEPLOYMENT");
if (openviduDeployment != null) {
OPENVIDU_DEPLOYMENT = openviduDeployment;
}
log.info("Using URL {} to connect to OpenVidu deployment", OPENVIDU_DEPLOYMENT);
}
protected BrowserUser setupBrowser(String browser) throws Exception {
@ -446,15 +455,16 @@ public class OpenViduTestE2e {
break;
case "ionicApp":
container = setupDockerAndroidContainer();
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-ionic.apk");
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-ionic.apk", true);
break;
case "reactNativeApp":
container = setupDockerAndroidContainer();
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-react-native.apk");
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-react-native.apk",
false);
break;
case "androidApp":
container = setupDockerAndroidContainer();
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-android.apk");
browserUser = new AndroidAppUser("TestUser", 50, "/opt/openvidu/android/openvidu-android.apk", false);
break;
default:
log.error("Browser {} not recognized", browser);

View File

@ -6,14 +6,13 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
@ -26,6 +25,8 @@ import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.springframework.http.HttpMethod;
import com.google.gson.JsonObject;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.remote.SupportsContextSwitching;
@ -57,23 +58,27 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
log.info("Android emulator ready after {} seconds", (System.currentTimeMillis() - initTime) / 1000);
log.info("Chrome Android");
String sessionName = "MobileTestSession-" + RandomStringUtils.randomAlphanumeric(6);
String sessionName = "MobileTestSession-" + UUID.randomUUID().toString().substring(0, 10);
WebElement urlInput = user.getDriver().findElement(By.id("openvidu-url"));
WebElement secretInput = user.getDriver().findElement(By.id("openvidu-secret"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
urlInput.sendKeys(OPENVIDU_URL);
secretInput.clear();
secretInput.sendKeys(OPENVIDU_SECRET);
String[] tokens = { getToken(sessionName), getToken(sessionName) };
user.getDriver().findElement(By.id("one2one-btn")).click();
user.getDriver().findElement(By.id("one2one-btn")).sendKeys(Keys.ENTER);
// Set tokens
for (int i = 0; i < 2; i++) {
user.getDriver().findElement(By.id("session-settings-btn-" + i)).click();
WebElement settingsBtn = user.getWaiter().until(
ExpectedConditions.elementToBeClickable(By.id("session-settings-btn-" + i)));
settingsBtn.sendKeys(Keys.ENTER);
Thread.sleep(1000);
WebElement tokenInput = user.getDriver().findElement(By.cssSelector("#custom-token-div input"));
tokenInput.clear();
tokenInput.sendKeys(tokens[i]);
user.getDriver().findElement(By.id("save-btn")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("save-btn")).sendKeys(Keys.ENTER);
}
user.getDriver().findElements(By.className("join-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
@ -127,12 +132,17 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
userNameInput.sendKeys(USER_NAME);
appiumDriver.findElement(By.cssSelector("#settings-button")).click();
Thread.sleep(500);
WebElement urlInput = appiumDriver.findElement(By.cssSelector("input.alert-input"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
appiumDriver.findElement(By.cssSelector("#ok-btn")).click();
Thread.sleep(2000);
WebElement urlInput = appiumDriver.findElement(By.cssSelector("#url-input"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_URL);
WebElement secretInput = appiumDriver.findElement(By.cssSelector("#secret-input"));
secretInput.clear();
secretInput.sendKeys(OPENVIDU_SECRET);
appiumDriver.findElement(By.cssSelector("#ok-btn")).click();
Thread.sleep(3000);
appiumDriver.findElement(By.cssSelector("#join-button")).click();
OpenViduTestappUser chromeUser = null;
@ -140,16 +150,20 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
chromeUser = setupBrowserAndConnectToOpenViduTestapp("chrome");
urlInput = chromeUser.getDriver().findElement(By.id("openvidu-url"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
chromeUser.getDriver().findElement(By.id("add-user-btn")).click();
chromeUser.getDriver().findElement(By.id("session-settings-btn-0")).click();
urlInput.sendKeys(OPENVIDU_URL);
secretInput = chromeUser.getDriver().findElement(By.id("openvidu-secret"));
secretInput.clear();
secretInput.sendKeys(OPENVIDU_SECRET);
chromeUser.getDriver().findElement(By.id("add-user-btn")).sendKeys(Keys.ENTER);
chromeUser.getDriver().findElement(By.id("session-settings-btn-0")).sendKeys(Keys.ENTER);
Thread.sleep(1000);
WebElement tokenInput = chromeUser.getDriver().findElement(By.cssSelector("#custom-token-div input"));
tokenInput.clear();
tokenInput.sendKeys(getToken(SESSION_NAME));
chromeUser.getDriver().findElement(By.id("save-btn")).click();
chromeUser.getDriver().findElement(By.id("save-btn")).sendKeys(Keys.ENTER);
Thread.sleep(1000);
chromeUser.getDriver().findElement(By.className("join-btn")).click();
chromeUser.getDriver().findElement(By.className("join-btn")).sendKeys(Keys.ENTER);
chromeUser.getEventManager().waitUntilEventReaches("connectionCreated", 2);
chromeUser.getEventManager().waitUntilEventReaches("accessAllowed", 1);
chromeUser.getEventManager().waitUntilEventReaches("streamCreated", 2);
@ -168,17 +182,6 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
ionicUser.assertMediaTracks(ionicUser.getDriver().findElements(By.tagName("video")), true, true),
"Videos were expected to have audio and video tracks");
// Check Ionic is properly receiving remote video from Chrome
WebElement subscriberVideo = ionicUser.getDriver().findElements(By.cssSelector("video")).get(1);
try {
Map<String, Long> rgb = ionicUser.getAverageRgbFromVideo(subscriberVideo);
Assertions.assertTrue(checkAverageRgbGreen(rgb), "Video is not average green");
} catch (Throwable e) {
log.error("Error checking average green of Ionic user");
System.out.println(getBase64Screenshot(ionicUser));
throw e;
}
gracefullyLeaveParticipants(chromeUser, 1);
} finally {
@ -223,10 +226,42 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
reactNativeUser.getWaiter()
.until(ExpectedConditions.elementToBeClickable(AppiumBy.className("android.widget.EditText")));
// Open settings modal to configure OpenVidu URL and Secret
List<WebElement> buttons = appiumDriver.findElements(By.className("android.widget.Button"));
log.info("Initial buttons count: {}", buttons.size());
WebElement settingsButton = buttons.get(2); // Third button is "Settings"
settingsButton.click();
// Wait for modal to open and find input fields
Thread.sleep(1000);
List<WebElement> editTexts = appiumDriver.findElements(AppiumBy.className("android.widget.EditText"));
log.info("EditTexts found: {}", editTexts.size());
// Modal shows 2 EditTexts: [0]=URL, [1]=Secret
WebElement androidUrlInput = editTexts.get(0);
androidUrlInput.clear();
androidUrlInput.sendKeys(OPENVIDU_URL);
WebElement androidSecretInput = editTexts.get(1);
androidSecretInput.clear();
androidSecretInput.sendKeys(OPENVIDU_SECRET);
// Click Save button - after modal opens, there are more buttons (Cancel and
// Save)
buttons = appiumDriver.findElements(By.className("android.widget.Button"));
log.info("Buttons after modal open: {}", buttons.size());
// Save button should be the last one
WebElement saveButton = buttons.get(buttons.size() - 1);
saveButton.click();
Thread.sleep(1000);
// Now configure session name and join
WebElement sessionNameInput = appiumDriver.findElement(AppiumBy.className("android.widget.EditText"));
sessionNameInput.clear();
sessionNameInput.sendKeys(SESSION_NAME);
List<WebElement> buttons = appiumDriver.findElements(By.className("android.widget.Button"));
buttons = appiumDriver.findElements(By.className("android.widget.Button"));
WebElement joinAsPublisherButton = buttons.get(0);
joinAsPublisherButton.click();
@ -235,7 +270,10 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
chromeUser = setupBrowserAndConnectToOpenViduTestapp("chrome");
WebElement urlInput = chromeUser.getDriver().findElement(By.id("openvidu-url"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
urlInput.sendKeys(OPENVIDU_URL);
WebElement secretInput = chromeUser.getDriver().findElement(By.id("openvidu-secret"));
secretInput.clear();
secretInput.sendKeys(OPENVIDU_SECRET);
chromeUser.getDriver().findElement(By.id("add-user-btn")).click();
chromeUser.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
@ -269,8 +307,17 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
reactNativeUser.getWaiter()
.until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(remoteVideoLocator)));
// Check subscriber video is green
// Check Android's subscriber video is green
Map<String, Long> rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator);
// Wait until average color is green
int retries = 15;
while (retries > 0 && !checkAverageRgbGreen(rgb)) {
Thread.sleep(1000);
rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator);
log.info("Average RGB for remote video in Android: R={} G={} B={}", rgb.get("r"), rgb.get("g"),
rgb.get("b"));
retries--;
}
Assertions.assertTrue(checkAverageRgbGreen(rgb), "Remote video is not average green");
buttons = appiumDriver.findElements(By.className("android.widget.Button"));
@ -335,13 +382,16 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
androidUser.getWaiter().until(ExpectedConditions.elementToBeClickable(By.id("start_finish_call")));
WebElement urlInput = appiumDriver.findElement(By.id("application_server_url"));
WebElement urlInput = appiumDriver.findElement(By.id("openvidu_url"));
WebElement secretInput = appiumDriver.findElement(By.id("openvidu_secret"));
WebElement sessionNameInput = appiumDriver.findElement(By.id("session_name"));
WebElement userNameInput = appiumDriver.findElement(By.id("participant_name"));
urlInput.clear();
secretInput.clear();
sessionNameInput.clear();
userNameInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
urlInput.sendKeys(OPENVIDU_URL);
secretInput.sendKeys(OPENVIDU_SECRET);
sessionNameInput.sendKeys(SESSION_NAME);
userNameInput.sendKeys(USER_NAME);
@ -352,7 +402,10 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
chromeUser = setupBrowserAndConnectToOpenViduTestapp("chrome");
urlInput = chromeUser.getDriver().findElement(By.id("openvidu-url"));
urlInput.clear();
urlInput.sendKeys(OPENVIDU_DEPLOYMENT);
urlInput.sendKeys(OPENVIDU_URL);
secretInput = chromeUser.getDriver().findElement(By.id("openvidu-secret"));
secretInput.clear();
secretInput.sendKeys(OPENVIDU_SECRET);
chromeUser.getDriver().findElement(By.id("add-user-btn")).click();
chromeUser.getDriver().findElement(By.id("session-settings-btn-0")).click();
Thread.sleep(1000);
@ -386,14 +439,23 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
androidUser.getWaiter()
.until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(remoteVideoLocator)));
// Check subscriber video is green
// Check Android's subscriber video is green
Map<String, Long> rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator);
// Wait until average color is green
int retries = 15;
while (retries > 0 && !checkAverageRgbGreen(rgb)) {
Thread.sleep(1000);
rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator);
log.info("Average RGB for remote video in Android: R={} G={} B={}", rgb.get("r"), rgb.get("g"),
rgb.get("b"));
retries--;
}
Assertions.assertTrue(checkAverageRgbGreen(rgb), "Remote video is not average green");
appiumDriver.findElement(By.id("start_finish_call")).click();
androidUser.getWaiter().until(
ExpectedConditions.elementToBeClickable(appiumDriver.findElement(By.id("application_server_url"))));
Assertions.assertTrue(appiumDriver.findElement(By.id("application_server_url")).isEnabled());
ExpectedConditions.elementToBeClickable(appiumDriver.findElement(By.id("openvidu_url"))));
Assertions.assertTrue(appiumDriver.findElement(By.id("openvidu_url")).isEnabled());
chromeUser.getEventManager().waitUntilEventReaches("streamDestroyed", 1);
chromeUser.getEventManager().waitUntilEventReaches("connectionDestroyed", 1);
@ -408,20 +470,22 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest {
}
private String getToken(String sessionId) throws Exception {
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_DEPLOYMENT);
restClient.restString(HttpMethod.POST, "/api/sessions", "{'customSessionId': '" + sessionId + "'}",
HttpURLConnection.HTTP_OK);
return restClient.restString(HttpMethod.POST, "/api/sessions/" + sessionId + "/connections", "{}",
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
try {
restClient.restString(HttpMethod.POST, "/openvidu/api/sessions", "{'customSessionId': '" + sessionId + "'}",
HttpURLConnection.HTTP_OK);
} catch (Exception e) {
restClient.restString(HttpMethod.POST, "/openvidu/api/sessions", "{'customSessionId': '" + sessionId + "'}",
HttpURLConnection.HTTP_CONFLICT);
}
JsonObject response = restClient.rest(HttpMethod.POST, "/openvidu/api/sessions/" + sessionId + "/connection",
"{}",
HttpURLConnection.HTTP_OK);
return response.get("token").getAsString();
}
private Map<String, Long> getAverageColorOfElement(WebDriver driver, By locator) throws IOException {
// TODO: change getScreenshotAs(OutputType.BASE64) to
// getScreenshotAs(OutputType.BYTES) when
// https://github.com/appium/java-client/issues/1783 is fixed
String base64 = driver.findElement(locator).getScreenshotAs(OutputType.BASE64);
base64 = base64.replaceAll("[\n\r]", "");
byte[] bytes = Base64.getDecoder().decode(base64);
byte[] bytes = driver.findElement(locator).getScreenshotAs(OutputType.BYTES);
InputStream is = new ByteArrayInputStream(bytes);
BufferedImage bi = ImageIO.read(is);
return averageColor(bi);