diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/changed.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/changed.java deleted file mode 100644 index 8fb06846c..000000000 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/changed.java +++ /dev/null @@ -1,5249 +0,0 @@ -/* - * (C) Copyright 2017-2022 OpenVidu (https://openvidu.io) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package io.openvidu.test.e2e; - -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.time.Duration; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; - -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.Keys; -import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import com.google.common.collect.ImmutableList; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.minio.BucketExistsArgs; -import io.minio.ListObjectsArgs; -import io.minio.MinioClient; -import io.minio.Result; -import io.minio.errors.ErrorResponseException; -import io.minio.errors.InsufficientDataException; -import io.minio.errors.InternalException; -import io.minio.errors.InvalidResponseException; -import io.minio.errors.ServerException; -import io.minio.errors.XmlParserException; -import io.minio.messages.Item; -import livekit.LivekitIngress.IngressInfo; -import livekit.LivekitIngress.IngressState; -import livekit.LivekitModels.ConnectionQuality; -import livekit.LivekitModels.ParticipantInfo; -import livekit.LivekitModels.TrackInfo; -import livekit.LivekitModels.TrackType; -import livekit.LivekitModels.VideoLayer; - -import static org.openqa.selenium.OutputType.BASE64; - -/** - * E2E tests for openvidu-testapp. - * - * @author Pablo Fuente (pablofuenteperez@gmail.com) - * @since 1.1.1 - */ -@Tag("e2e") -@DisplayName("E2E tests for OpenVidu TestApp") -@ExtendWith(SpringExtension.class) -public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { - - @BeforeAll() - protected static void setupAll() throws Exception { - checkFfmpegInstallation(); - loadEnvironmentVariables(); - setUpLiveKitClient(); - CompletableFuture.runAsync(() -> { - try { - NetworkConditioner.pullImages(); - } catch (Exception e) { - System.err.println("Download of images failed: " + e.getMessage()); - } - }); - CompletableFuture.runAsync(OpenViduTestAppE2eTest::pullRemoteBrowserImages); - } - - private static void pullRemoteBrowserImages() { - pullRemoteBrowserImage("REMOTE_URL_CHROME", "selenium/standalone-chrome:" + CHROME_VERSION); - pullRemoteBrowserImage("REMOTE_URL_FIREFOX", "selenium/standalone-firefox:" + FIREFOX_VERSION); - pullRemoteBrowserImage("REMOTE_URL_EDGE", "selenium/standalone-edge:" + EDGE_VERSION); - } - - private static void pullRemoteBrowserImage(String remoteUrlProperty, String image) { - if (System.getProperty(remoteUrlProperty) == null) { - return; // This browser runs as a native driver here. No Docker image to pull - } - try { - log.info("Pre-pulling Selenium image {}", image); - commandLine.executeCommand("docker pull " + image, 300); - } catch (Exception e) { - System.err.println("Pre-pull of " + image + " failed: " + e.getMessage()); - } - } - - @BeforeEach() - protected void setupEach() { - this.closeAllRooms(LK); - this.deleteAllIngresses(LK_INGRESS); - } - - @AfterEach() - protected void finishEach() { - this.closeAllRooms(LK); - this.deleteAllIngresses(LK_INGRESS); - } - - @Test - @DisplayName("One2One Chrome") - void oneToOneChrome() throws Exception { - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - log.info("One2One Chrome"); - oneToOneAux(user); - } - - @Test - @DisplayName("One2One Firefox") - void oneToOneFirefox() throws Exception { - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("firefox"); - log.info("One2One Firefox"); - oneToOneAux(user); - } - - @Test - @DisplayName("One2One Edge") - void oneToOneEdge() throws Exception { - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("edge"); - log.info("One2One Edge"); - oneToOneAux(user); - } - - private void oneToOneAux(OpenViduTestappUser user) throws Exception { - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - user.getDriver().findElement(By.id("one2one-btn")).click(); - user.getEventManager().waitUntilEventReaches("signalConnected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("participantActive", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches("active", "ParticipantEvent", 3); - user.getEventManager().waitUntilEventReaches("connectionStateChanged", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("localTrackPublished", "ParticipantEvent", 4); - user.getEventManager().waitUntilEventReaches("localTrackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 4); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 4); - user.getEventManager().waitUntilEventReaches("trackSubscriptionStatusChanged", "RoomEvent", 8); - user.getEventManager().waitUntilEventReaches("trackSubscriptionStatusChanged", "ParticipantEvent", 8); - user.getEventManager().waitUntilEventReaches("visibilityChanged", "TrackEvent", 2); - - user.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 4)); - user.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("audio"), 4)); - final int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size(); - final int numberOfAudios = user.getDriver().findElements(By.tagName("audio")).size(); - Assertions.assertEquals(4, numberOfVideos, "Wrong number of videos"); - Assertions.assertEquals(4, numberOfAudios, "Wrong number of audios"); - - Assertions.assertTrue(assertAllElementsHaveTracks(user, "video", false, true), - "HTMLVideoElements were expected to have only one video track"); - Assertions.assertTrue(assertAllElementsHaveTracks(user, "audio.remote", true, false), - "HTMLAudioElements were expected to have only one audio track"); - gracefullyLeaveParticipants(user, 2); - } - - @Test - @DisplayName("Signal Reliable DataChannel") - void signalReliableTest() throws Exception { - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - log.info("Signal Reliable DataChannel"); - signalReliableLossyAux(user, true); - } - - @Test - @DisplayName("Signal Lossy DataChannel") - void signalLossyTest() throws Exception { - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - log.info("Signal Lossy DataChannel"); - signalReliableLossyAux(user, false); - } - - private void signalReliableLossyAux(OpenViduTestappUser user, boolean reliable) throws Exception { - final int expectedKind = reliable ? 0 : 1; // DataPacket_Kind: RELIABLE=0, LOSSY=1 - final String expectedKindStr = reliable ? "RELIABLE" : "LOSSY"; - final String btnClass = reliable ? ".message-reliable-btn" : ".message-lossy-btn"; - - for (int i = 0; i < 2; i++) { - WebElement addUserBtn = user.getDriver().findElement(By.id("add-user-btn")); - addUserBtn.click(); - user.getDriver().findElement(By.cssSelector("#openvidu-instance-" + i + " .subscriber-checkbox")).click(); - user.getDriver().findElement(By.cssSelector("#openvidu-instance-" + i + " .publisher-checkbox")).click(); - } - user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); - user.getEventManager().waitUntilEventReaches("signalConnected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("connectionStateChanged", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("participantActive", "RoomEvent", 1); - - Collection> assertions = new ArrayList<>(); - List kindAssertions = Collections.synchronizedList(new ArrayList<>()); - - // Broadcast from TestParticipant0 - final CountDownLatch broadcastLatch0 = new CountDownLatch(2); - - user.getEventManager().on(1, "dataReceived", "RoomEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant0 to all room (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - broadcastLatch0.countDown(); - }); - user.getEventManager().on(1, "dataReceived", "ParticipantEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant0 to all room (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - broadcastLatch0.countDown(); - }); - - user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 " + btnClass)).click(); - user.getEventManager().waitUntilEventReaches(1, "dataReceived", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches(1, "dataReceived", "ParticipantEvent", 1); - // Do not trigger own signals - Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataReceived-RoomEvent").get()); - Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataReceived-ParticipantEvent").get()); - - if (!broadcastLatch0.await(3, TimeUnit.SECONDS)) { - Assertions.fail("Timeout waiting for broadcast signal event from TestParticipant0"); - } - assertions.forEach(assertion -> Assertions.assertEquals(assertion.getKey(), assertion.getValue())); - kindAssertions.forEach( - kind -> Assertions.assertEquals(expectedKind, kind, "Expected DataPacket_Kind " + expectedKind)); - user.getEventManager().off(1, "dataReceived", "RoomEvent"); - user.getEventManager().off(1, "dataReceived", "ParticipantEvent"); - assertions.clear(); - kindAssertions.clear(); - user.getEventManager().clearAllCurrentEvents(); - - // Broadcast from TestParticipant1 - final CountDownLatch broadcastLatch1 = new CountDownLatch(2); - - user.getEventManager().on(0, "dataReceived", "RoomEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant1 to all room (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - broadcastLatch1.countDown(); - }); - user.getEventManager().on(0, "dataReceived", "ParticipantEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant1 to all room (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - broadcastLatch1.countDown(); - }); - user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 " + btnClass)).click(); - user.getEventManager().waitUntilEventReaches(0, "dataReceived", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches(0, "dataReceived", "ParticipantEvent", 1); - // Do not trigger own signals - Assertions.assertEquals(1, user.getEventManager().getNumEvents(0, "dataReceived-RoomEvent").get()); - Assertions.assertEquals(1, user.getEventManager().getNumEvents(0, "dataReceived-ParticipantEvent").get()); - - if (!broadcastLatch1.await(3, TimeUnit.SECONDS)) { - Assertions.fail("Timeout waiting for broadcast signal event from TestParticipant1"); - } - assertions.forEach(assertion -> Assertions.assertEquals(assertion.getKey(), assertion.getValue())); - kindAssertions.forEach( - kind -> Assertions.assertEquals(expectedKind, kind, "Expected DataPacket_Kind " + expectedKind)); - user.getEventManager().off(0, "dataReceived", "RoomEvent"); - user.getEventManager().off(0, "dataReceived", "ParticipantEvent"); - assertions.clear(); - kindAssertions.clear(); - user.getEventManager().clearAllCurrentEvents(); - - // Signal specific participant - - // Signal from TestParticipant0 to TestParticipant1 - final CountDownLatch directLatch0 = new CountDownLatch(2); - - user.getEventManager().on(1, "dataReceived", "RoomEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant0 to TestParticipant1 (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - directLatch0.countDown(); - }); - user.getEventManager().on(1, "dataReceived", "ParticipantEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant0 to TestParticipant1 (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - directLatch0.countDown(); - }); - user.getDriver() - .findElement( - By.cssSelector("#openvidu-instance-0 app-participant.remote-participant " + btnClass)) - .click(); - user.getEventManager().waitUntilEventReaches(1, "dataReceived", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches(1, "dataReceived", "ParticipantEvent", 1); - // Do not trigger own signals - Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataReceived-RoomEvent").get()); - Assertions.assertEquals(0, user.getEventManager().getNumEvents(0, "dataReceived-ParticipantEvent").get()); - - if (!directLatch0.await(3, TimeUnit.SECONDS)) { - Assertions.fail("Timeout waiting for direct signal event from TestParticipant0"); - } - assertions.forEach(assertion -> Assertions.assertEquals(assertion.getKey(), assertion.getValue())); - kindAssertions.forEach( - kind -> Assertions.assertEquals(expectedKind, kind, "Expected DataPacket_Kind " + expectedKind)); - user.getEventManager().off(1, "dataReceived", "RoomEvent"); - user.getEventManager().off(1, "dataReceived", "ParticipantEvent"); - assertions.clear(); - kindAssertions.clear(); - user.getEventManager().clearAllCurrentEvents(); - - // Signal from TestParticipant1 to TestParticipant0 - final CountDownLatch directLatch1 = new CountDownLatch(2); - - user.getEventManager().on(0, "dataReceived", "RoomEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant1 to TestParticipant0 (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - directLatch1.countDown(); - }); - user.getEventManager().on(0, "dataReceived", "ParticipantEvent", json -> { - assertions.add(new AbstractMap.SimpleEntry<>( - "Message from TestParticipant1 to TestParticipant0 (kind: " + expectedKindStr + ")", - json.getAsJsonObject().get("eventDescription").getAsString())); - kindAssertions.add(json.getAsJsonObject().get("eventContent").getAsJsonObject().get("kind").getAsInt()); - directLatch1.countDown(); - }); - user.getDriver() - .findElement( - By.cssSelector("#openvidu-instance-1 app-participant.remote-participant " + btnClass)) - .click(); - user.getEventManager().waitUntilEventReaches(0, "dataReceived", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches(0, "dataReceived", "ParticipantEvent", 1); - // Do not trigger own signals - Assertions.assertEquals(0, user.getEventManager().getNumEvents(1, "dataReceived-RoomEvent").get()); - Assertions.assertEquals(0, user.getEventManager().getNumEvents(1, "dataReceived-ParticipantEvent").get()); - - if (!directLatch1.await(3, TimeUnit.SECONDS)) { - Assertions.fail("Timeout waiting for direct signal event from TestParticipant1"); - } - assertions.forEach(assertion -> Assertions.assertEquals(assertion.getKey(), assertion.getValue())); - kindAssertions.forEach( - kind -> Assertions.assertEquals(expectedKind, kind, "Expected DataPacket_Kind " + expectedKind)); - - gracefullyLeaveParticipants(user, 2); - } - - @Test - @DisplayName("ConnectionQualityChanged") - void connectionQualityChangedTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQualityChanged"); - - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - user.getDriver().findElement(By.id("one2one-btn")).click(); - - user.getEventManager().waitUntilEventReaches("signalConnected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "ParticipantEvent", 4); - - // Expect the connection quality events to include as text content: "excellent" - user.getDriver().findElements(By.cssSelector(".connectionQualityChanged-TestParticipant0 .event-content")) - .forEach(el -> Assertions.assertTrue(el.getText().contains("excellent"), - "Expected connection quality to be excellent")); - user.getDriver().findElements(By.cssSelector(".connectionQualityChanged-TestParticipant1 .event-content")) - .forEach(el -> Assertions.assertTrue(el.getText().contains("excellent"), - "Expected connection quality to be excellent")); - - gracefullyLeaveParticipants(user, 2); - } - - private List getConnectionQualityEventContents(OpenViduTestappUser user, int numberOfUser, - String participantName) { - String xpath = "//*[@id='openvidu-instance-" + numberOfUser - + "']//mat-expansion-panel[.//mat-expansion-panel-header[contains(@class,'connectionQualityChanged-" - + participantName + "')]]//div[contains(@class,'event-content')]"; - return user.getDriver().findElements(By.xpath(xpath)); - } - - // Latest connection-quality level currently reported for a participant, or null - // if none yet. - private ConnectionQuality latestConnectionQuality(OpenViduTestappUser user, int numberOfUser, - String participantName) { - List contents = getConnectionQualityEventContents(user, numberOfUser, participantName); - for (int i = contents.size() - 1; i >= 0; i--) { - String text = contents.get(i).getAttribute("textContent"); - if (text == null) { - continue; - } - text = text.toLowerCase().trim(); - if (text.contains("excellent")) { - return ConnectionQuality.EXCELLENT; - } - if (text.contains("good")) { - return ConnectionQuality.GOOD; - } - if (text.contains("poor")) { - return ConnectionQuality.POOR; - } - if (text.contains("lost")) { - return ConnectionQuality.LOST; - } - } - return null; - } - - // First loss% (ascending) at which the recorded quality equals `level`, or -1 - // if never reached. - private static int firstLossReaching(Map observed, ConnectionQuality level) { - for (Entry e : observed.entrySet()) { - if (e.getValue() == level) { - return e.getKey(); - } - } - return -1; - } - - // Render the ramp results (publisher vs subscriber-self quality per loss step) - // + the band transitions as a single log table. - private static String buildRampResultTable(Map publisher, - Map subscriber) { - StringBuilder sb = new StringBuilder("\nConnectionQuality ramp-up results\n"); - sb.append(" loss% | PunchbagUser (publisher) | RegularUser (subscriber, self)\n"); - sb.append(" ------+--------------------------+-------------------------------\n"); - ConnectionQuality prev = null; - for (Entry e : publisher.entrySet()) { - int pct = e.getKey(); - ConnectionQuality pub = e.getValue(); - ConnectionQuality sub = subscriber.get(pct); - String transition = (prev != null && pub != prev) ? " <- " + prev + " -> " + pub : ""; - sb.append(String.format(" %4d%% | %-24s | %-30s%s%n", pct, String.valueOf(pub), String.valueOf(sub), - transition)); - prev = pub; - } - sb.append(" transitions: EXCELLENT->GOOD @").append(firstLossReaching(publisher, ConnectionQuality.GOOD)) - .append("%, GOOD->POOR @").append(firstLossReaching(publisher, ConnectionQuality.POOR)) - .append("%, POOR->LOST @").append(firstLossReaching(publisher, ConnectionQuality.LOST)).append("%"); - return sb.toString(); - } - - private void assertConnectionQualityNeverDegraded(OpenViduTestappUser user, int numberOfUser, - String participantName) { - List contents = getConnectionQualityEventContents(user, numberOfUser, participantName); - Assertions.assertFalse(contents.isEmpty(), "Expected at least one connectionQualityChanged event for " - + participantName + " in openvidu-instance-" + numberOfUser); - String latest = null; - for (WebElement el : contents) { - String quality = el.getAttribute("textContent"); - if (quality == null) { - continue; - } - quality = quality.toLowerCase().trim(); - if (quality.isEmpty()) { - continue; - } - Assertions.assertFalse(quality.contains("poor") || quality.contains("lost"), - "Connection quality for " + participantName + " (openvidu-instance-" + numberOfUser - + ") must never be POOR or LOST under server-side-only conditions, but observed: '" - + quality - + "'"); - latest = quality; - } - Assertions.assertNotNull(latest, - "No connection quality value found for " + participantName + " in openvidu-instance-" + numberOfUser); - Assertions.assertTrue(latest.contains("excellent"), - "Connection quality for " + participantName + " (openvidu-instance-" + numberOfUser - + ") should settle on EXCELLENT, but the latest value was: '" + latest + "'"); - } - - @Test - @DisplayName("ConnectionQuality EXCELLENT with many published and subscribed tracks") - void connectionQualityManyTracksAlwaysExcellentTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQuality EXCELLENT with many published and subscribed tracks"); - - final int N = 3; - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - WebElement nInput = user.getDriver().findElement(By.id("one2many-input")); - nInput.clear(); - nInput.sendKeys(String.valueOf(N)); - user.getDriver().findElement(By.id("one2one-btn")).click(); - - // N participants, each publishing audio+video and subscribing to all others - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", N); - user.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 2 * N); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", N); - - // Let quality settle and give any (wrong) degradation time to surface - Thread.sleep(CONNECTION_QUALITY_OBSERVATION_MS); - - for (int i = 0; i < N; i++) { - assertConnectionQualityNeverDegraded(user, i, "TestParticipant" + i); - } - - gracefullyLeaveParticipants(user, N); - } - - @Test - @DisplayName("ConnectionQuality EXCELLENT when publisher pauses a track") - void connectionQualityPublisherPauseAlwaysExcellentTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQuality EXCELLENT when publisher pauses a track"); - - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - user.getDriver().findElement(By.id("one2one-btn")).click(); - - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", 2); - - // Publisher (instance 0) pauses (mutes) its video track -> the scorer heals to - // EXCELLENT (mute -> cMaxScore), so quality must not degrade. - user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .mute-unmute-video")).sendKeys(Keys.ENTER); - user.getEventManager().waitUntilEventReaches("trackMuted", "RoomEvent", 2); - - Thread.sleep(CONNECTION_QUALITY_OBSERVATION_MS); - - assertConnectionQualityNeverDegraded(user, 0, "TestParticipant0"); - assertConnectionQualityNeverDegraded(user, 1, "TestParticipant1"); - - gracefullyLeaveParticipants(user, 2); - } - - @Test - @DisplayName("ConnectionQuality EXCELLENT when subscriber pauses a track") - void connectionQualitySubscriberPauseAlwaysExcellentTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQuality EXCELLENT when subscriber pauses a track"); - - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - user.getDriver().findElement(By.id("one2one-btn")).click(); - - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", 2); - - // Subscriber (instance 1) disables playback of the subscribed video track -> - // the - // DownTrack forwarder is muted and the downstream scorer heals to EXCELLENT. - user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .toggle-video-enabled")).sendKeys(Keys.ENTER); - - Thread.sleep(CONNECTION_QUALITY_OBSERVATION_MS); - - assertConnectionQualityNeverDegraded(user, 0, "TestParticipant0"); - assertConnectionQualityNeverDegraded(user, 1, "TestParticipant1"); - - gracefullyLeaveParticipants(user, 2); - } - - @Test - @DisplayName("ConnectionQuality EXCELLENT when Dynacast pauses an unsubscribed track") - void connectionQualityDynacastPauseAlwaysExcellentTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQuality EXCELLENT when Dynacast pauses an unsubscribed track"); - - // Instance 0: video-only publisher with Dynacast + simulcast enabled. - this.addPublisher(user, false, true, true, false, false, true, null, null, null); - // Instance 1: subscriber only. - this.addSubscriber(user, true); - - user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); - - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 1); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", 1); - - // Subscriber unsubscribes from the only track -> the publisher's track becomes - // fully unwatched -> Dynacast pauses it -> the publisher stops sending and the - // mediasoup Producer score would decay to 0. Connection quality MUST stay - // EXCELLENT, NOT drop to POOR/LOST. - user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .toggle-video-subscribed")) - .sendKeys(Keys.ENTER); - - // Generous wait: Dynacast pause delay + producer inactivity (~1.5s) + EMA + - // room ticks - Thread.sleep(CONNECTION_QUALITY_OBSERVATION_MS + 10000); - - assertConnectionQualityNeverDegraded(user, 0, "TestParticipant0"); - - gracefullyLeaveParticipants(user, 2); - } - - @Test - @DisplayName("ConnectionQuality EXCELLENT when Adaptive Stream pauses a track") - void connectionQualityAdaptiveStreamPauseAlwaysExcellentTest() throws Exception { - - OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); - - log.info("ConnectionQuality EXCELLENT when Adaptive Stream pauses a track"); - - // one2one publishes audio+video with Adaptive Stream enabled (testapp default). - user.getDriver().findElement(By.id("auto-join-checkbox")).click(); - user.getDriver().findElement(By.id("one2one-btn")).click(); - - user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 2); - user.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 4); - user.getEventManager().waitUntilEventReaches("connectionQualityChanged", "RoomEvent", 2); - - // Hide the remote video elements so Adaptive Stream pauses those subscriptions - // (Adaptive Stream pauses tracks whose