openvidu-testapp: keep remote media elements when participants or publications change (stable @for keys)

pull/907/head
pabloFuente 2026-09-05 14:04:38 +02:00
parent 146e09a7b0
commit 8594da8c49
4 changed files with 76 additions and 8 deletions

View File

@ -154,11 +154,6 @@ public class OpenViduTestAppE2eServerSdkTest extends AbstractOpenViduTestappE2eT
log.info("{} SDK {} {}-layer publisher to Chrome and Firefox subscribers", sdk, codec, layers); log.info("{} SDK {} {}-layer publisher to Chrome and Firefox subscribers", sdk, codec, layers);
// Both browsers join the room as subscriber-only participants with
// adaptiveStream disabled (the received layer only changes through the
// testapp's max-video-quality selector), each with its own identity (a
// second join with the testapp's default identity would kick the first
// browser out of the room)
for (OpenViduTestappUser user : subscribers) { for (OpenViduTestappUser user : subscribers) {
this.addSubscriber(user, false); this.addSubscriber(user, false);
WebElement participantNameInput = user.getDriver().findElement(By.id("participant-name-input-0")); WebElement participantNameInput = user.getDriver().findElement(By.id("participant-name-input-0"));
@ -167,6 +162,9 @@ public class OpenViduTestAppE2eServerSdkTest extends AbstractOpenViduTestappE2eT
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("connected", "RoomEvent", 1); user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 1);
} }
for (OpenViduTestappUser user : subscribers) {
user.getEventManager().waitUntilEventReaches("active", "ParticipantEvent", 1);
}
this.startServerSdkPublisher(sdk, "TestRoom", codec, multiLayer); this.startServerSdkPublisher(sdk, "TestRoom", codec, multiLayer);

View File

@ -55,6 +55,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys; import org.openqa.selenium.Keys;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
@ -4740,4 +4741,73 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
this.waitUntilSubscriberFrameWidthIs(user, subscriberVideo, 1920); this.waitUntilSubscriberFrameWidthIs(user, subscriberVideo, 1920);
} }
@Test
@DisplayName("Remote video element survives participant changes")
void remoteVideoElementSurvivesParticipantChangesTest() throws Exception {
// openvidu-testapp test: it must keep the DOM elements of the remote tracks
// already rendered when the remote participant list changes
OpenViduTestappUser publisher = setupBrowserAndConnectToOpenViduTestapp("chrome");
OpenViduTestappUser subscriber = setupBrowserAndConnectToOpenViduTestapp("chrome");
OpenViduTestappUser newcomer = setupBrowserAndConnectToOpenViduTestapp("chrome");
log.info("Remote video element survives participant changes");
this.addOnlyPublisherVideo(publisher, false, false, false);
this.joinRoomAs(publisher, "publisher");
publisher.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 1);
this.addSubscriber(subscriber, false);
this.joinRoomAs(subscriber, "subscriber");
subscriber.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 1);
subscriber.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 1));
WebElement remoteVideo = subscriber.getDriver()
.findElement(By.cssSelector("#openvidu-instance-0 video.remote"));
this.waitUntilSubscriberFramesPerSecondNotZero(subscriber, remoteVideo);
// A participant joins: the subscriber's remote participant list grows
this.addSubscriber(newcomer, false);
this.joinRoomAs(newcomer, "newcomer");
subscriber.getEventManager().waitUntilEventReaches("participantConnected", "RoomEvent", 1);
this.assertRemoteVideoElementKept(subscriber, remoteVideo, 1, "a participant joined");
// The publisher publishes a second video track: its publication list grows
publisher.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .add-video-btn")).click();
publisher.getEventManager().waitUntilEventReaches("localTrackPublished", "RoomEvent", 2);
subscriber.getEventManager().waitUntilEventReaches("trackSubscribed", "RoomEvent", 2);
subscriber.getWaiter().until(ExpectedConditions.numberOfElementsToBe(By.tagName("video"), 2));
this.assertRemoteVideoElementKept(subscriber, remoteVideo, 2, "the publisher published a second track");
// A participant leaves: the subscriber's remote participant list shrinks
gracefullyLeaveParticipants(newcomer, 1);
subscriber.getEventManager().waitUntilEventReaches("participantDisconnected", "RoomEvent", 1);
this.assertRemoteVideoElementKept(subscriber, remoteVideo, 2, "a participant left");
// And the kept element is still a playing video
this.waitUntilSubscriberFramesPerSecondNotZero(subscriber, remoteVideo);
gracefullyLeaveParticipants(subscriber, 1);
gracefullyLeaveParticipants(publisher, 1);
}
private void joinRoomAs(OpenViduTestappUser user, String identity) throws Exception {
WebElement participantNameInput = user.getDriver().findElement(By.id("participant-name-input-0"));
participantNameInput.clear();
participantNameInput.sendKeys(identity);
user.getDriver().findElements(By.className("connect-btn")).forEach(el -> el.sendKeys(Keys.ENTER));
user.getEventManager().waitUntilEventReaches("connected", "RoomEvent", 1);
}
private void assertRemoteVideoElementKept(OpenViduTestappUser user, WebElement remoteVideo, int expectedVideos,
String change) {
try {
Assertions.assertTrue(remoteVideo.isDisplayed(),
"The remote video element is no longer displayed after " + change);
} catch (StaleElementReferenceException e) {
Assertions.fail("The remote video element was re-created after " + change
+ ": the WebElement located before is stale", e);
}
Assertions.assertEquals(expectedVideos, user.getDriver().findElements(By.tagName("video")).size(),
"Wrong number of videos after " + change);
}
} }

View File

@ -95,7 +95,7 @@
[index]="index" [participantEvents]="participantEvents" [trackEvents]="trackEvents" [index]="index" [participantEvents]="participantEvents" [trackEvents]="trackEvents"
[earlyParticipantEvents]="earlyParticipantEvents" [earlyParticipantListeners]="earlyParticipantListeners" [earlyParticipantEvents]="earlyParticipantEvents" [earlyParticipantListeners]="earlyParticipantListeners"
[earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"></app-participant> [earlyTrackEvents]="earlyTrackEvents" [earlyTrackListeners]="earlyTrackListeners"></app-participant>
@for (participant of room.remoteParticipants | keyvalue; track participant) { @for (participant of room.remoteParticipants | keyvalue; track participant.value.sid) {
<app-participant class="remote-participant" <app-participant class="remote-participant"
[participant]="participant.value" [room]="room" [index]="index" [participant]="participant.value" [room]="room" [index]="index"
[participantEvents]="participantEvents" [trackEvents]="trackEvents" [participantEvents]="participantEvents" [trackEvents]="trackEvents"

View File

@ -102,7 +102,7 @@
} }
</div> </div>
<div class="audio-tracks-container"> <div class="audio-tracks-container">
@for (trackPublication of participant.audioTrackPublications| keyvalue; track trackPublication) { @for (trackPublication of participant.audioTrackPublications | keyvalue; track trackPublication.key) {
<app-audio-track <app-audio-track
[index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.audioTrack" [index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.audioTrack"
[localParticipant]="localParticipant" [localParticipant]="localParticipant"
@ -110,7 +110,7 @@
(newTrackEvent)="onTrackEvent($event)"></app-audio-track> (newTrackEvent)="onTrackEvent($event)"></app-audio-track>
} }
</div> </div>
@for (trackPublication of participant.videoTrackPublications | keyvalue; track trackPublication) { @for (trackPublication of participant.videoTrackPublications | keyvalue; track trackPublication.key) {
<app-video-track <app-video-track
[index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.videoTrack" [index]="index" [trackPublication]="trackPublication.value" [track]="trackPublication.value.videoTrack"
[localParticipant]="localParticipant" [localParticipant]="localParticipant"