openvidu-test-e2e: improve test execution time

pull/907/head
pabloFuente 2026-09-02 19:02:57 +02:00
parent d61aca3135
commit 73c3d581bd
1 changed files with 124 additions and 46 deletions

View File

@ -93,6 +93,8 @@ import static org.openqa.selenium.OutputType.BASE64;
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
private static final long WAIT_UNTIL_MAX_MILLIS = 20000;
@BeforeAll() @BeforeAll()
protected static void setupAll() throws Exception { protected static void setupAll() throws Exception {
checkFfmpegInstallation(); checkFfmpegInstallation();
@ -3379,33 +3381,42 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@Test @Test
@DisplayName("SVC VP9 (L3T3_KEY)") @DisplayName("SVC VP9 (L3T3_KEY)")
void svcVP9L3T3_KEYTest() throws Exception { void svcVP9L3T3_KEYTest() throws Exception {
svcTest("VP9", "L3T3_KEY"); svcTest("VP9", "L3T3_KEY", false);
} }
@Test @Test
@DisplayName("SVC AV1 (L3T3_KEY)") @DisplayName("SVC AV1 (L3T3_KEY)")
void svcAV1L3T3_KEYTest() throws Exception { void svcAV1L3T3_KEYTest() throws Exception {
svcTest("AV1", "L3T3_KEY"); svcTest("AV1", "L3T3_KEY", false);
} }
@Test @Test
@DisplayName("SVC VP9 (L2T2)") @DisplayName("SVC VP9 (L2T2)")
void svcVP9L2T2Test() throws Exception { void svcVP9L2T2Test() throws Exception {
svcTest("VP9", "L2T2"); svcTest("VP9", "L2T2", false);
} }
@Test @Test
@DisplayName("SVC AV1 (L2T2)") @DisplayName("SVC AV1 (L2T2)")
void svcAV1L2T2Test() throws Exception { void svcAV1L2T2Test() throws Exception {
svcTest("AV1", "L2T2"); svcTest("AV1", "L2T2", false);
} }
private void svcTest(String codec, String scalabilityMode) throws Exception { @Test
@DisplayName("SVC AV1 (L2T2) with adaptiveStream")
void svcAV1L2T2WithAdaptiveStreamTest() throws Exception {
svcTest("AV1", "L2T2", true);
}
private void svcTest(String codec, String scalabilityMode, boolean adaptiveStream) throws Exception {
final String codecUpperCase = codec.toUpperCase(); final String codecUpperCase = codec.toUpperCase();
final long testStart = System.currentTimeMillis();
log.info("[SVC] START test: codec={}, scalabilityMode={}, adaptiveStream={}", codec, scalabilityMode,
adaptiveStream);
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome"); OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
this.addOnlyPublisherVideo(user, false, false, true, scalabilityMode); this.addOnlyPublisherVideo(user, false, false, true, scalabilityMode);
this.forceCodec(user, 0, codec); this.forceCodec(user, 0, codec);
this.addSubscriber(user, false); this.addSubscriber(user, adaptiveStream);
user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER)); user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
user.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1); user.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1);
user.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 1); user.getEventManager().waitUntilEventReaches("trackSubscribed", "ParticipantEvent", 1);
@ -3428,24 +3439,54 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
// Validate SVC by dynamically switching subscriber quality and checking // Validate SVC by dynamically switching subscriber quality and checking
// subscriber frameWidth transitions. // subscriber frameWidth transitions.
// With adaptiveStream disabled, quality is switched manually through the
// "max-video-quality" selector, which lets HIGH/MEDIUM/LOW be requested
// explicitly. With adaptiveStream enabled, that manual selector has no
// effect: the spatial layer is instead derived from the rendered size of
// the subscriber's video element
int spatialLayers = Integer.parseInt(scalabilityMode.substring(1, 2)); int spatialLayers = Integer.parseInt(scalabilityMode.substring(1, 2));
int highWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int highWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click(); user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300); Thread.sleep(300);
if (spatialLayers >= 3) { if (adaptiveStream) {
this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "HIGH");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, highWidth, true);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int bigWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300);
this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "LOW");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, bigWidth, false);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int smallWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertTrue(bigWidth > smallWidth,
"Expected the zoomed-in width to exceed the zoomed-out width, but got big=" + bigWidth
+ ", small=" + smallWidth);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300);
this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "HIGH");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, smallWidth, true);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int finalBigWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertEquals(bigWidth, finalBigWidth,
"Expected the zoomed-in width to be restored, but got first=" + bigWidth + ", second="
+ finalBigWidth);
} else if (spatialLayers >= 3) {
// 3 spatial layers (e.g. L3T3_KEY): layers are LOW, MEDIUM, HIGH. // 3 spatial layers (e.g. L3T3_KEY): layers are LOW, MEDIUM, HIGH.
// HIGH → MEDIUM → LOW, each a distinct lower resolution. // HIGH → MEDIUM → LOW, each a distinct lower resolution.
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "MEDIUM");
this.waitForBackdropAndClick(user, "mat-option.mode-MEDIUM");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, highWidth, false); this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, highWidth, false);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int mediumWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int mediumWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click(); user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300); Thread.sleep(300);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "LOW");
this.waitForBackdropAndClick(user, "mat-option.mode-LOW");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, mediumWidth, false); this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, mediumWidth, false);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int lowWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int lowWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertTrue(highWidth > mediumWidth && mediumWidth > lowWidth, Assertions.assertTrue(highWidth > mediumWidth && mediumWidth > lowWidth,
"Expected HIGH > MEDIUM > LOW frame widths, but got HIGH=" + highWidth "Expected HIGH > MEDIUM > LOW frame widths, but got HIGH=" + highWidth
@ -3453,17 +3494,16 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click(); user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300); Thread.sleep(300);
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "HIGH");
this.waitForBackdropAndClick(user, "mat-option.mode-HIGH");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, lowWidth, true); this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, lowWidth, true);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
} else { } else {
// 2 spatial layers (e.g. L2T2): layers are LOW and MEDIUM only. // 2 spatial layers (e.g. L2T2): layers are LOW and MEDIUM only.
// HIGH and MEDIUM both map to the highest spatial layer (same width). // HIGH and MEDIUM both map to the highest spatial layer (same width).
// Only LOW gives a distinct lower resolution. // Only LOW gives a distinct lower resolution.
// 1. Switch to MEDIUM: should stay at the same width as HIGH // 1. Switch to MEDIUM: should stay at the same width as HIGH
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "MEDIUM");
this.waitForBackdropAndClick(user, "mat-option.mode-MEDIUM");
Thread.sleep(4000); Thread.sleep(4000);
int mediumWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int mediumWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertEquals(highWidth, mediumWidth, Assertions.assertEquals(highWidth, mediumWidth,
@ -3471,11 +3511,12 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
+ highWidth + ", MEDIUM=" + mediumWidth); + highWidth + ", MEDIUM=" + mediumWidth);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click(); user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300); Thread.sleep(300);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
// 2. Switch to LOW: should decrease // 2. Switch to LOW: should decrease
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "LOW");
this.waitForBackdropAndClick(user, "mat-option.mode-LOW");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, mediumWidth, false); this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, mediumWidth, false);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int lowWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int lowWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertTrue(highWidth > lowWidth, Assertions.assertTrue(highWidth > lowWidth,
"Expected HIGH > LOW frame widths, but got HIGH=" + highWidth + ", LOW=" + lowWidth); "Expected HIGH > LOW frame widths, but got HIGH=" + highWidth + ", LOW=" + lowWidth);
@ -3483,23 +3524,48 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Thread.sleep(300); Thread.sleep(300);
// 3. Switch to MEDIUM: should increase back to highest spatial layer // 3. Switch to MEDIUM: should increase back to highest spatial layer
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "MEDIUM");
this.waitForBackdropAndClick(user, "mat-option.mode-MEDIUM");
this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, lowWidth, true); this.waitUntilSubscriberFrameWidthChanges(user, subscriberVideo, lowWidth, true);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
int mediumWidth2 = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int mediumWidth2 = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click(); user.getDriver().findElement(By.cssSelector("#close-dialog-btn")).click();
Thread.sleep(300); Thread.sleep(300);
// 4. Switch to HIGH: should stay at the same width as MEDIUM // 4. Switch to HIGH: should stay at the same width as MEDIUM
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click(); this.switchSubscriberSpatialLayer(user, subscriberVideo, adaptiveStream, "HIGH");
this.waitForBackdropAndClick(user, "mat-option.mode-HIGH");
Thread.sleep(4000); Thread.sleep(4000);
int finalHighWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo); int finalHighWidth = this.getSubscriberVideoFrameWidth(user, subscriberVideo);
Assertions.assertEquals(mediumWidth2, finalHighWidth, Assertions.assertEquals(mediumWidth2, finalHighWidth,
"With " + spatialLayers + " spatial layers, HIGH should equal MEDIUM width, but got MEDIUM=" "With " + spatialLayers + " spatial layers, HIGH should equal MEDIUM width, but got MEDIUM="
+ mediumWidth2 + ", HIGH=" + finalHighWidth); + mediumWidth2 + ", HIGH=" + finalHighWidth);
this.waitUntilSubscriberFramesDecodedIncrease(user, subscriberVideo);
} }
gracefullyLeaveParticipants(user, 2); gracefullyLeaveParticipants(user, 2);
log.info("[SVC] END test: codec={}, scalabilityMode={}, adaptiveStream={}, totalDuration={} ms", codec,
scalabilityMode, adaptiveStream, System.currentTimeMillis() - testStart);
}
// With adaptiveStream disabled, the subscriber's preferred quality is
// switched manually through the "max-video-quality" selector. With
// adaptiveStream enabled, that manual selector is ignored by the client SDK,
// which instead derives the desired spatial layer from the rendered size of
// the subscriber's video element, always picking the smallest layer that is
// sufficient for that size. Only "LOW" (small element) and "HIGH" (large
// element) are meaningful in that case.
private void switchSubscriberSpatialLayer(OpenViduTestappUser user, WebElement subscriberVideo,
boolean adaptiveStream, String quality) {
if (adaptiveStream) {
if ("LOW".equals(quality)) {
changeElementSize(user, subscriberVideo, 80, 100);
} else {
changeElementSize(user, subscriberVideo, 1000, 700);
}
} else {
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 #max-video-quality")).click();
this.waitForBackdropAndClick(user, "mat-option.mode-" + quality);
}
} }
@Test @Test
@ -4364,18 +4430,26 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
private <T> T getSubscriberVideoLayerStat(OpenViduTestappUser user, WebElement subscriberVideo, String field, private <T> T getSubscriberVideoLayerStat(OpenViduTestappUser user, WebElement subscriberVideo, String field,
java.util.function.Function<JsonElement, T> extractor) { java.util.function.Function<JsonElement, T> extractor) {
waitUntilVideoLayersNotEmpty(user, subscriberVideo); final long deadline = System.currentTimeMillis() + WAIT_UNTIL_MAX_MILLIS;
final java.util.concurrent.atomic.AtomicReference<T> value = new java.util.concurrent.atomic.AtomicReference<>(); JsonElement element = null;
this.waitUntilAux(user, subscriberVideo, () -> { do {
JsonElement element = getLayersAsJsonArray(user, subscriberVideo).get(0).getAsJsonObject().get(field); try {
if (element != null && !element.isJsonNull()) { element = getLayersAsJsonArray(user, subscriberVideo).get(0).getAsJsonObject().get(field);
value.set(extractor.apply(element)); } catch (Exception e) {
return true; element = null;
} }
return false; if (element == null || element.isJsonNull()) {
}, "Timeout waiting for " + field + " to exist"); try {
openInfoDialog(user, subscriberVideo); Thread.sleep(250);
return value.get(); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} while ((element == null || element.isJsonNull()) && System.currentTimeMillis() < deadline);
if (element == null || element.isJsonNull()) {
Assertions.fail("Timeout waiting for " + field + " to exist");
}
return extractor.apply(element);
} }
// If rid is null, retrieve the first layer // If rid is null, retrieve the first layer
@ -4406,10 +4480,10 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
} }
private void waitUntilVideoLayersNotEmpty(OpenViduTestappUser user, WebElement videoElement) { private void waitUntilVideoLayersNotEmpty(OpenViduTestappUser user, WebElement videoElement) {
this.waitUntilAux(user, videoElement, this.waitUntilAux(user, videoElement, () -> {
() -> !getLayersAsString(user, videoElement).isBlank() String value = getLayersAsString(user, videoElement);
&& !getLayersAsJsonArray(user, videoElement).isEmpty(), return !value.isBlank() && !JsonParser.parseString(value).getAsJsonArray().isEmpty();
"Timeout waiting video layers to not be empty"); }, "Timeout waiting video layers to not be empty");
} }
private void waitUntilSubscriberFramesPerSecondNotZero(OpenViduTestappUser user, WebElement videoElement) { private void waitUntilSubscriberFramesPerSecondNotZero(OpenViduTestappUser user, WebElement videoElement) {
@ -4502,13 +4576,10 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
private void waitUntilAux(OpenViduTestappUser user, WebElement videoElement, private void waitUntilAux(OpenViduTestappUser user, WebElement videoElement,
Callable<Boolean> breakFromLoopFunction, String errMsg) { Callable<Boolean> breakFromLoopFunction, String errMsg) {
try { try {
final int maxWaitMillis = 6000; final long intervalWait = 250;
final int intervalWait = 250; final long deadline = System.currentTimeMillis() + WAIT_UNTIL_MAX_MILLIS;
final int MAX_ITERATIONS = maxWaitMillis / intervalWait;
int iteration = 0;
boolean breakFromLoop = false; boolean breakFromLoop = false;
while (!breakFromLoop && iteration < MAX_ITERATIONS) { while (!breakFromLoop && System.currentTimeMillis() < deadline) {
iteration++;
try { try {
breakFromLoop = breakFromLoopFunction.call(); breakFromLoop = breakFromLoopFunction.call();
} catch (Exception e1) { } catch (Exception e1) {
@ -4543,23 +4614,30 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
private void openInfoDialog(OpenViduTestappUser user, WebElement video) { private void openInfoDialog(OpenViduTestappUser user, WebElement video) {
String videoId = video.getDomProperty("id"); String videoId = video.getDomProperty("id");
// Open the track info dialog if required // Open the track info dialog if required
boolean dialogWasOpened;
if (!user.getDriver().findElements(By.cssSelector("app-info-dialog")).isEmpty()) { if (!user.getDriver().findElements(By.cssSelector("app-info-dialog")).isEmpty()) {
// Dialog already opened // Dialog already opened
if (!user.getDriver().findElement(By.cssSelector("#subtitle")).getText().equals(videoId)) { if (!user.getDriver().findElement(By.cssSelector("#subtitle")).getText().equals(videoId)) {
// Wrong dialog // Wrong dialog
this.waitForBackdropAndClick(user, "#close-dialog-btn"); this.waitForBackdropAndClick(user, "#close-dialog-btn");
this.waitForBackdropAndClick(user, "#" + videoId + " ~ .bottom-div .video-track-info"); this.waitForBackdropAndClick(user, "#" + videoId + " ~ .bottom-div .video-track-info");
dialogWasOpened = true;
} else {
dialogWasOpened = false;
} }
} else { } else {
// Dialog is not opened // Dialog is not opened
this.waitForBackdropAndClick(user, "#" + videoId + " ~ .bottom-div .video-track-info"); this.waitForBackdropAndClick(user, "#" + videoId + " ~ .bottom-div .video-track-info");
dialogWasOpened = true;
} }
if (dialogWasOpened) {
try { try {
Thread.sleep(300); Thread.sleep(300);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
}
private void addPublisherSubscriber(OpenViduTestappUser user, boolean hasAudio, boolean hasVideo) private void addPublisherSubscriber(OpenViduTestappUser user, boolean hasAudio, boolean hasVideo)
throws InterruptedException { throws InterruptedException {