diff --git a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java index fedfc0fc..b1c6869f 100644 --- a/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java +++ b/openvidu-test-e2e/src/main/java/io/openvidu/test/e2e/OpenViduTestE2e.java @@ -193,7 +193,7 @@ public class OpenViduTestE2e { private static GenericContainer androidContainer(String image, long shmSize) { GenericContainer android = new GenericContainer<>(DockerImageName.parse(image)).withPrivilegedMode(true) .withEnv(Map.of("DEVICE", "Samsung Galaxy S10", "APPIUM", "true", "APPIUM_HOST", "172.17.0.1", - "APPIUM_PORT", "4723", "MOBILE_WEB_TEST", "true", "RELAXED_SECURITY", "true")) + "APPIUM_PORT", "4723", "MOBILE_WEB_TEST", "true", "RELAXED_SECURITY", "true", "DATAPARTITION", "2500m")) .withSharedMemorySize(shmSize).withExposedPorts(6080, 5554, 5555, 4723).waitingFor(waitAndroid) .withFileSystemBind("/opt/openvidu/android", "/opt/openvidu/android").withReuse(true); android.setPortBindings(Arrays.asList("6080:6080", "5554:5554", "5555:5555", "4723:4723")); diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduMobileE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduMobileE2eTest.java index 6f0df723..694dd616 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduMobileE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduMobileE2eTest.java @@ -1,8 +1,17 @@ package io.openvidu.test.e2e; +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.imageio.ImageIO; + import org.apache.commons.lang3.RandomStringUtils; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Assertions; @@ -11,15 +20,17 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.Keys; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.springframework.http.HttpMethod; +import io.appium.java_client.AppiumBy; import io.appium.java_client.AppiumDriver; import io.appium.java_client.remote.SupportsContextSwitching; import io.openvidu.test.browsers.BrowserUser; import io.openvidu.test.browsers.utils.CustomHttpClient; -import io.openvidu.test.browsers.utils.RecordingUtils; public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest { @@ -160,7 +171,226 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest { // Check Ionic is properly receiving remote video from Chrome WebElement subscriberVideo = ionicUser.getDriver().findElements(By.cssSelector("video")).get(1); Map rgb = ionicUser.getAverageRgbFromVideo(subscriberVideo); - Assertions.assertTrue(RecordingUtils.checkVideoAverageRgbGreen(rgb), "Video is not average green"); + Assertions.assertTrue(checkAverageRgbGreen(rgb), "Video is not average green"); + + gracefullyLeaveParticipants(chromeUser, 1); + + } finally { + if (chromeUser != null) { + chromeUser.dispose(); + } + } + } + + /** + * By default this application is prepared to run against + * https://demos.openvidu.io + */ + @Test + @DisplayName("React Native Android") + void reactNativeAndroid() throws Exception { + + isAndroidTest = true; + + long initTime = System.currentTimeMillis(); + BrowserUser reactNativeUser = setupBrowser("reactNativeApp"); + log.info("Android emulator ready after {} seconds", (System.currentTimeMillis() - initTime) / 1000); + log.info("React Native Android"); + + AppiumDriver appiumDriver = (AppiumDriver) reactNativeUser.getDriver(); + + Set contextNames = ((SupportsContextSwitching) appiumDriver).getContextHandles(); + log.info("Appium contexts:"); + for (String contextName : contextNames) { + log.info(contextName); + } + for (String context : contextNames) { + if (context.equals("NATIVE_APP")) { + log.info("Webview context name chosen is " + context); + ((SupportsContextSwitching) appiumDriver).context("NATIVE_APP"); + break; + } + } + + final String SESSION_NAME = "ReactNativeTestSession"; + + reactNativeUser.getWaiter() + .until(ExpectedConditions.elementToBeClickable(AppiumBy.className("android.widget.EditText"))); + + WebElement sessionNameInput = appiumDriver.findElement(AppiumBy.className("android.widget.EditText")); + sessionNameInput.clear(); + sessionNameInput.sendKeys(SESSION_NAME); + List buttons = appiumDriver.findElements(By.className("android.widget.Button")); + WebElement joinAsPublisherButton = buttons.get(0); + joinAsPublisherButton.click(); + + OpenViduTestappUser chromeUser = null; + try { + chromeUser = setupBrowserAndConnectToOpenViduTestapp("chrome"); + WebElement 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(); + 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(); + Thread.sleep(1000); + chromeUser.getDriver().findElement(By.className("join-btn")).click(); + chromeUser.getEventManager().waitUntilEventReaches("connectionCreated", 2); + chromeUser.getEventManager().waitUntilEventReaches("accessAllowed", 1); + chromeUser.getEventManager().waitUntilEventReaches("streamCreated", 2); + chromeUser.getEventManager().waitUntilEventReaches("streamPlaying", 2); + + final int numberOfVideos = chromeUser.getDriver().findElements(By.tagName("video")).size(); + Assertions.assertEquals(2, numberOfVideos, "Wrong number of videos"); + Assertions.assertTrue( + chromeUser.getBrowserUser() + .assertMediaTracks(chromeUser.getDriver().findElements(By.tagName("video")), true, true), + "Videos were expected to have audio and video tracks"); + + // Wait for Android app to have 2 video views + // Publisher view + By localVideoLocator = AppiumBy.xpath( + "//*/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[1]/android.view.View"); + reactNativeUser.getWaiter() + .until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(localVideoLocator))); + // Subscriber view + By remoteVideoLocator = AppiumBy.xpath( + "//*/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup[2]/android.view.View"); + reactNativeUser.getWaiter() + .until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(remoteVideoLocator))); + + // Check subscriber video is green + Map rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator); + Assertions.assertTrue(checkAverageRgbGreen(rgb), "Remote video is not average green"); + + buttons = appiumDriver.findElements(By.className("android.widget.Button")); + WebElement muteMicBtn = buttons.get(1); + WebElement muteCamBtn = buttons.get(3); + WebElement leaveBtn = buttons.get(4); + + muteMicBtn.click(); + chromeUser.getEventManager().waitUntilEventReaches("streamPropertyChanged", 1); + muteMicBtn.click(); + chromeUser.getEventManager().waitUntilEventReaches("streamPropertyChanged", 2); + muteCamBtn.click(); + chromeUser.getEventManager().waitUntilEventReaches("streamPropertyChanged", 3); + muteCamBtn.click(); + chromeUser.getEventManager().waitUntilEventReaches("streamPropertyChanged", 4); + leaveBtn.click(); + + reactNativeUser.getWaiter().until( + ExpectedConditions.visibilityOfElementLocated(AppiumBy.className("android.widget.EditText"))); + Assertions.assertTrue(appiumDriver.findElement(AppiumBy.className("android.widget.EditText")).isEnabled()); + + chromeUser.getEventManager().waitUntilEventReaches("streamDestroyed", 1); + chromeUser.getEventManager().waitUntilEventReaches("connectionDestroyed", 1); + + gracefullyLeaveParticipants(chromeUser, 1); + + } finally { + if (chromeUser != null) { + chromeUser.dispose(); + } + } + } + + @Test + @DisplayName("Native Android") + void nativeAndroid() throws Exception { + + isAndroidTest = true; + + long initTime = System.currentTimeMillis(); + BrowserUser androidUser = setupBrowser("androidApp"); + log.info("Android emulator ready after {} seconds", (System.currentTimeMillis() - initTime) / 1000); + log.info("Native Android"); + + AppiumDriver appiumDriver = (AppiumDriver) androidUser.getDriver(); + + Set contextNames = ((SupportsContextSwitching) appiumDriver).getContextHandles(); + log.info("Appium contexts:"); + for (String contextName : contextNames) { + log.info(contextName); + } + for (String context : contextNames) { + if (context.equals("NATIVE_APP")) { + log.info("Webview context name chosen is " + context); + ((SupportsContextSwitching) appiumDriver).context("NATIVE_APP"); + break; + } + } + + final String SESSION_NAME = "NativeAndroidTestSession"; + final String USER_NAME = "NativeAndroidUser"; + + androidUser.getWaiter().until(ExpectedConditions.elementToBeClickable(By.id("start_finish_call"))); + + WebElement urlInput = appiumDriver.findElement(By.id("application_server_url")); + WebElement sessionNameInput = appiumDriver.findElement(By.id("session_name")); + WebElement userNameInput = appiumDriver.findElement(By.id("participant_name")); + urlInput.clear(); + sessionNameInput.clear(); + userNameInput.clear(); + urlInput.sendKeys(OPENVIDU_DEPLOYMENT); + sessionNameInput.sendKeys(SESSION_NAME); + userNameInput.sendKeys(USER_NAME); + + appiumDriver.findElement(By.id("start_finish_call")).click(); + + OpenViduTestappUser chromeUser = null; + try { + 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(); + 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(); + Thread.sleep(1000); + chromeUser.getDriver().findElement(By.className("join-btn")).click(); + chromeUser.getEventManager().waitUntilEventReaches("connectionCreated", 2); + chromeUser.getEventManager().waitUntilEventReaches("accessAllowed", 1); + chromeUser.getEventManager().waitUntilEventReaches("streamCreated", 2); + chromeUser.getEventManager().waitUntilEventReaches("streamPlaying", 2); + + final int numberOfVideos = chromeUser.getDriver().findElements(By.tagName("video")).size(); + Assertions.assertEquals(2, numberOfVideos, "Wrong number of videos"); + Assertions.assertTrue( + chromeUser.getBrowserUser() + .assertMediaTracks(chromeUser.getDriver().findElements(By.tagName("video")), true, true), + "Videos were expected to have audio and video tracks"); + + // Wait for Android app to have 2 video views + // Publisher view + By localVideoLocator = AppiumBy.xpath( + "//*/android.widget.ScrollView/android.widget.LinearLayout/android.widget.FrameLayout[1]/android.view.View"); + androidUser.getWaiter() + .until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(localVideoLocator))); + // Subscriber view + By remoteVideoLocator = AppiumBy.xpath( + "//*/android.widget.ScrollView/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.view.View"); + androidUser.getWaiter() + .until(ExpectedConditions.elementToBeClickable(appiumDriver.findElement(remoteVideoLocator))); + + // Check subscriber video is green + Map rgb = getAverageColorOfElement(appiumDriver, remoteVideoLocator); + 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()); + + chromeUser.getEventManager().waitUntilEventReaches("streamDestroyed", 1); + chromeUser.getEventManager().waitUntilEventReaches("connectionDestroyed", 1); gracefullyLeaveParticipants(chromeUser, 1); @@ -179,20 +409,37 @@ public class OpenViduMobileE2eTest extends AbstractOpenViduTestappE2eTest { HttpStatus.SC_OK); } -// @Test -// @DisplayName("React Native Android") -// void reactNativeAndroid() throws Exception { -// long initTime = System.currentTimeMillis(); -// BrowserUser reactNativeUser = setupBrowser("reactNativeApp"); -// -// } -// -// @Test -// @DisplayName("Native Android") -// void nativeAndroid() throws Exception { -// long initTime = System.currentTimeMillis(); -// BrowserUser reactNativeUser = setupBrowser("androidApp"); -// -// } + private Map 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); + System.out.println(base64); + base64 = base64.replaceAll("[\n\r]", ""); + byte[] bytes = Base64.getDecoder().decode(base64); + InputStream is = new ByteArrayInputStream(bytes); + BufferedImage bi = ImageIO.read(is); + return averageColor(bi); + } + + private Map averageColor(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + long sumr = 0, sumg = 0, sumb = 0; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + Color pixel = new Color(image.getRGB(x, y)); + sumr += pixel.getRed(); + sumg += pixel.getGreen(); + sumb += pixel.getBlue(); + } + } + int numPixels = width * height; + return Map.of("r", (sumr / numPixels), "g", (sumg / numPixels), "b", (sumb / numPixels)); + } + + private boolean checkAverageRgbGreen(Map rgb) { + return (rgb.get("r") < 30) && (rgb.get("g") > 120) && (rgb.get("b") < 30); + } }