openvidu-test-e2e: new network quality e2e test

pull/553/head
pabloFuente 2020-11-05 17:33:25 +01:00
parent c3d9fbb60e
commit df0530d0a8
6 changed files with 194 additions and 52 deletions

View File

@ -140,7 +140,7 @@ node('container') {
mvn --batch-mode versions:set-property -Dproperty=version.openvidu.java.client -DnewVersion=TEST
mvn --batch-mode versions:set-property -Dproperty=version.openvidu.test.browsers -DnewVersion=TEST
cd openvidu-test-e2e
mvn -DskipTests=true clean compile
mvn -DskipTests=true clean install
sudo mvn --batch-mode -Dtest=OpenViduTestAppE2eTest -DAPP_URL=https://172.17.0.1:4200/ -DOPENVIDU_URL=https://172.17.0.1:4443/ -DREMOTE_URL_CHROME=http://172.17.0.1:6666/wd/hub/ -DREMOTE_URL_FIREFOX=http://172.17.0.1:6667/wd/hub/ -DEXTERNAL_CUSTOM_LAYOUT_URL=http://172.17.0.1:5555 -DEXTERNAL_CUSTOM_LAYOUT_PARAMS=sessionId,CUSTOM_LAYOUT_SESSION,secret,MY_SECRET test
git checkout -f $OPENVIDU_COMMIT
'''.stripIndent())

View File

@ -283,9 +283,8 @@ public class OpenViduEventManager {
}
String[] events = rawEvents.replaceFirst("^<br>", "").split("<br>");
JsonParser parser = new JsonParser();
for (String e : events) {
JsonObject event = (JsonObject) parser.parse(e);
JsonObject event = JsonParser.parseString(e).getAsJsonObject();
final String eventType = event.get("type").getAsString();
this.eventQueue.add(event);

View File

@ -1,13 +1,22 @@
package io.openvidu.test.e2e;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.FileReader;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.http.HttpStatus;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -36,6 +45,8 @@ import io.openvidu.test.browsers.utils.Unzipper;
public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
protected volatile static boolean isNetworkQualityTest;
@BeforeAll()
protected static void setupAll() {
checkFfmpegInstallation();
@ -44,6 +55,29 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
cleanFoldersAndSetUpOpenViduJavaClient();
}
@Override
@AfterEach
protected void dispose() {
super.dispose();
if (isNetworkQualityTest) {
// Disable network quality API
try {
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
if (restClient.rest(HttpMethod.GET, "/openvidu/api/config", 200).get("OPENVIDU_PRO_NETWORK_QUALITY")
.getAsBoolean()) {
String body = "{'OPENVIDU_PRO_NETWORK_QUALITY':false}";
restClient.rest(HttpMethod.POST, "/openvidu/api/restart", body, 200);
waitUntilOpenViduRestarted(30);
}
} catch (Exception e) {
log.error(e.getMessage());
Assert.fail("Error restarting OpenVidu Server to disable Network quality API");
} finally {
isNetworkQualityTest = false;
}
}
}
@Test
@DisplayName("Individual dynamic record")
void individualDynamicRecordTest() throws Exception {
@ -449,6 +483,9 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
@Test
@DisplayName("openvidu-java-client PRO test")
void openViduJavaClientProTest() throws Exception {
log.info("openvidu-java-client PRO test");
Session session = OV.createSession();
Assert.assertFalse(session.fetch());
Connection connection = session.createConnection();
@ -462,4 +499,101 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
Assert.assertFalse(session.fetch());
}
@Test
@DisplayName("Network quality test")
void networkQualityTest() throws Exception {
isNetworkQualityTest = true;
log.info("Network quality test");
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
String body = "{'OPENVIDU_PRO_NETWORK_QUALITY':true, 'OPENVIDU_PRO_NETWORK_QUALITY_INTERVAL':5}";
restClient.rest(HttpMethod.POST, "/openvidu/api/restart", body, 200);
waitUntilOpenViduRestarted(30);
setupBrowser("chrome");
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
JsonObject res = restClient.rest(HttpMethod.GET, "/openvidu/api/sessions/TestSession/connection",
HttpStatus.SC_OK);
final String connectionId = res.getAsJsonObject().get("content").getAsJsonArray().get(0).getAsJsonObject()
.get("id").getAsString();
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .publish-checkbox")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click();
final CountDownLatch latch1 = new CountDownLatch(1);
Queue<Boolean> threadAssertions = new ConcurrentLinkedQueue<Boolean>();
user.getEventManager().on("networkQualityLevelChanged", (event) -> {
try {
threadAssertions.add("networkQualityLevelChanged".equals(event.get("type").getAsString()));
threadAssertions.add(event.get("oldValue") == null);
threadAssertions.add(event.has("newValue") && event.get("newValue").getAsInt() > 0
&& event.get("newValue").getAsInt() < 6);
latch1.countDown();
} catch (Exception e) {
log.error("Error analysing NetworkQualityLevelChangedEvent: {}. {}", e.getCause(), e.getMessage());
fail("Error analysing NetworkQualityLevelChangedEvent: " + e.getCause() + ". " + e.getMessage());
}
});
user.getEventManager().waitUntilEventReaches("connectionCreated", 4);
user.getEventManager().waitUntilEventReaches("streamPlaying", 2);
user.getEventManager().waitUntilEventReaches("networkQualityLevelChanged", 2);
if (!latch1.await(30000, TimeUnit.MILLISECONDS)) {
gracefullyLeaveParticipants(1);
fail();
return;
}
user.getEventManager().off("networkQualityLevelChanged");
log.info("Thread assertions: {}", threadAssertions.toString());
for (Iterator<Boolean> iter = threadAssertions.iterator(); iter.hasNext();) {
Assert.assertTrue("Some Event property was wrong", iter.next());
iter.remove();
}
// Both events should have publisher's connection ID
Assert.assertTrue("Wrong connectionId in event NetworkQualityLevelChangedEvent", user.getDriver()
.findElement(By.cssSelector("#openvidu-instance-0 .mat-expansion-panel:last-child .event-content"))
.getAttribute("textContent").contains(connectionId));
Assert.assertTrue("Wrong connectionId in event NetworkQualityLevelChangedEvent", user.getDriver()
.findElement(By.cssSelector("#openvidu-instance-1 .mat-expansion-panel:last-child .event-content"))
.getAttribute("textContent").contains(connectionId));
gracefullyLeaveParticipants(1);
}
private void waitUntilOpenViduRestarted(int maxSecondsWait) throws Exception {
boolean restarted = false;
int msInterval = 500;
int attempts = 0;
final int maxAttempts = maxSecondsWait * 1000 / msInterval;
Thread.sleep(500);
while (!restarted && attempts < maxAttempts) {
try {
CustomHttpClient restClient = new CustomHttpClient(OPENVIDU_URL, "OPENVIDUAPP", OPENVIDU_SECRET);
restClient.rest(HttpMethod.GET, "/openvidu/api/health", 200);
restarted = true;
} catch (Exception e) {
try {
log.warn("Waiting for OpenVidu Server...");
Thread.sleep(msInterval);
} catch (InterruptedException e1) {
log.error("Sleep interrupted");
}
attempts++;
}
}
if (!restarted && attempts == maxAttempts) {
throw new TimeoutException();
}
}
}

View File

@ -18,16 +18,16 @@
</mat-form-field>
<div fxLayout="column" class="session-btns-div">
<button mat-icon-button title="Session properties" [id]="'session-settings-btn-' + index" class="mat-icon-custom"
(click)="openSessionPropertiesDialog()" [disabled]="session">
<button mat-icon-button title="Session properties" [id]="'session-settings-btn-' + index"
class="mat-icon-custom" (click)="openSessionPropertiesDialog()" [disabled]="session">
<mat-icon class="mat-icon-custom-ic" aria-label="Session properties button">settings</mat-icon>
</button>
<button mat-icon-button title="Session API" [id]="'session-api-btn-' + index" class="mat-icon-custom"
(click)="openSessionApiDialog()">
<mat-icon class="mat-icon-custom-ic" aria-label="Session API button">cloud_circle</mat-icon>
</button>
<button mat-icon-button title="Session events" [id]="'session-events-btn-' + index" class="mat-icon-custom"
(click)="openSessionEventsDialog()">
<button mat-icon-button title="Session events" [id]="'session-events-btn-' + index"
class="mat-icon-custom" (click)="openSessionEventsDialog()">
<mat-icon class="mat-icon-custom-ic" aria-label="Session events button">notifications</mat-icon>
</button>
</div>
@ -36,8 +36,8 @@
<div class="join-publish-div">
<button class="join-btn" mat-button (click)="joinSession()" [disabled]="session">JOIN</button>
<mat-checkbox class="subscribe-checkbox" name="subscribeTo" (click)="toggleSubscribeTo()" [checked]="subscribeTo"
[disabled]="session">Subscribe</mat-checkbox>
<mat-checkbox class="subscribe-checkbox" name="subscribeTo" (click)="toggleSubscribeTo()"
[checked]="subscribeTo" [disabled]="session">Subscribe</mat-checkbox>
<mat-checkbox class="publish-checkbox" name="publishTo" (click)="togglePublishTo()" [checked]="publishTo"
[disabled]="session">Publish</mat-checkbox>
</div>
@ -48,26 +48,32 @@
<div>
<h4>Send</h4>
<div>
<mat-checkbox class="send-audio-checkbox" name="sendAudio" (click)="toggleSendAudio()" [checked]="publisherProperties.audioSource !== false"
[disabled]="session || !publishTo">Audio</mat-checkbox>
<mat-checkbox class="send-video-checkbox" name="sendVideo" (click)="toggleSendVideo()" [checked]="publisherProperties.videoSource !== false"
[disabled]="session || !publishTo">Video</mat-checkbox>
<mat-checkbox class="send-audio-checkbox" name="sendAudio" (click)="toggleSendAudio()"
[checked]="publisherProperties.audioSource !== false" [disabled]="session || !publishTo">Audio
</mat-checkbox>
<mat-checkbox class="send-video-checkbox" name="sendVideo" (click)="toggleSendVideo()"
[checked]="publisherProperties.videoSource !== false" [disabled]="session || !publishTo">Video
</mat-checkbox>
</div>
</div>
<div style="padding-top: 5px;">
<h4>Enter active</h4>
<div>
<mat-checkbox class="active-audio-checkbox" name="activeAudio" [(ngModel)]="publisherProperties.publishAudio"
(click)="publisherProperties.publishAudio = !publisherProperties.publishAudio" [disabled]="session || !publishTo">Audio</mat-checkbox>
<mat-checkbox class="active-video-checkbox" name="activeVideo" [(ngModel)]="publisherProperties.publishVideo"
(click)="publisherProperties.publishVideo = !publisherProperties.publishVideo" [disabled]="session || !publishTo">Video</mat-checkbox>
<mat-checkbox class="active-audio-checkbox" name="activeAudio"
[(ngModel)]="publisherProperties.publishAudio"
(click)="publisherProperties.publishAudio = !publisherProperties.publishAudio"
[disabled]="session || !publishTo">Audio</mat-checkbox>
<mat-checkbox class="active-video-checkbox" name="activeVideo"
[(ngModel)]="publisherProperties.publishVideo"
(click)="publisherProperties.publishVideo = !publisherProperties.publishVideo"
[disabled]="session || !publishTo">Video</mat-checkbox>
</div>
</div>
</div>
<div fxFlex="35">
<mat-radio-group [(ngModel)]="optionsVideo" (change)="updateOptionsVideo($event)" [disabled]="session || !publishTo"
[ngModelOptions]="{standalone: true}">
<mat-radio-group [(ngModel)]="optionsVideo" (change)="updateOptionsVideo($event)"
[disabled]="session || !publishTo" [ngModelOptions]="{standalone: true}">
<div>
<mat-radio-button class="video-radio" value="video">Video</mat-radio-button>
</div>
@ -75,15 +81,16 @@
<mat-radio-button class="screen-radio" value="screen">Screen</mat-radio-button>
</div>
</mat-radio-group>
<mat-checkbox class="subscribe-remote-check" name="subscribeToRemote" (click)="subscribeToRemote = !subscribeToRemote"
[disabled]="!publishTo || session" [checked]="publishTo && subscribeToRemote">Subscribe
<mat-checkbox class="subscribe-remote-check" name="subscribeToRemote"
(click)="subscribeToRemote = !subscribeToRemote" [disabled]="!publishTo || session"
[checked]="publishTo && subscribeToRemote">Subscribe
<br>to remote</mat-checkbox>
</div>
<div fxFlex="10">
<div fxLayout="column" class="publisher-btns-div">
<button mat-icon-button title="Publisher properties" [id]="'publisher-settings-btn-' + index" class="mat-icon-custom"
(click)="openPublisherPropertiesDialog()" [disabled]="!publishTo">
<button mat-icon-button title="Publisher properties" [id]="'publisher-settings-btn-' + index"
class="mat-icon-custom" (click)="openPublisherPropertiesDialog()" [disabled]="!publishTo">
<mat-icon class="mat-icon-custom-ic" aria-label="Publisher properties button">settings</mat-icon>
</button>
<button mat-icon-button title="Add new publisher to running session" [id]="'session-api-btn-' + index"
@ -107,7 +114,8 @@
<div class="session-card-inner">
<div class="session-title">{{sessionName}}</div>
<div class="session-actions">
<button *ngIf="republishPossible" class="republish-error-btn" (click)="republishAfterError()" title="Re publish">
<button *ngIf="republishPossible" class="republish-error-btn" (click)="republishAfterError()"
title="Re publish">
<mat-icon aria-label="Re publish video" style="font-size: 20px">linked_camera</mat-icon>
</button>
<button class="message-btn" (click)="sendMessage()" title="Broadcast message">
@ -136,8 +144,8 @@
</div>
<div [attr.id]="'remote-vid-' + session.connection.connectionId" fxFlex="240px" class="video-container">
<div [attr.id]="'local-vid-' + session.connection.connectionId"></div>
<app-video *ngIf="this.publisher" [streamManager]="this.publisher" [OV]="OV" [properties]="publisherProperties"
(updateEventListInParent)="updateEventFromChild($event)">
<app-video *ngIf="this.publisher" [streamManager]="this.publisher" [OV]="OV"
[properties]="publisherProperties" (updateEventListInParent)="updateEventFromChild($event)">
</app-video>
<app-video *ngFor="let subscriber of this.subscribers" [streamManager]="subscriber" [OV]="OV"
(updateEventListInParent)="updateEventFromChild($event)" (reSubbed)="updateSubscriberFromChild($event)">

View File

@ -6,7 +6,7 @@ import {
import {
OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent,
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent
} from 'openvidu-browser';
import {
OpenVidu as OpenViduAPI,
@ -116,6 +116,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
streamDestroyed: true,
streamPropertyChanged: true,
connectionPropertyChanged: true,
networkQualityLevelChanged: true,
recordingStarted: true,
recordingStopped: true,
signal: true,
@ -225,6 +226,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
streamDestroyed: false,
streamPropertyChanged: false,
connectionPropertyChanged: false,
networkQualityLevelChanged: false,
recordingStarted: false,
recordingStopped: false,
signal: false,
@ -383,6 +385,15 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
}
if (this.sessionEvents.networkQualityLevelChanged !== oldValues.networkQualityLevelChanged || firstTime) {
this.session.off('networkQualityLevelChanged');
if (this.sessionEvents.networkQualityLevelChanged) {
this.session.on('networkQualityLevelChanged', (event: NetworkQualityLevelChangedEvent) => {
this.updateEventList('networkQualityLevelChanged', event.connection.connectionId + ' [new:' + event.newValue + ',old:' + event.oldValue + ']', event);
});
}
}
if (this.sessionEvents.connectionCreated !== oldValues.connectionCreated || firstTime) {
this.session.off('connectionCreated');
if (this.sessionEvents.connectionCreated) {
@ -638,6 +649,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
streamDestroyed: this.sessionEvents.streamDestroyed,
streamPropertyChanged: this.sessionEvents.streamPropertyChanged,
connectionPropertyChanged: this.sessionEvents.connectionPropertyChanged,
networkQualityLevelChanged: this.sessionEvents.networkQualityLevelChanged,
recordingStarted: this.sessionEvents.recordingStarted,
recordingStopped: this.sessionEvents.recordingStopped,
signal: this.sessionEvents.signal,
@ -671,6 +683,7 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
streamDestroyed: result.streamDestroyed,
streamPropertyChanged: result.streamPropertyChanged,
connectionPropertyChanged: result.connectionPropertyChanged,
networkQualityLevelChanged: result.networkQualityLevelChanged,
recordingStarted: result.recordingStarted,
recordingStopped: result.recordingStopped,
signal: result.signal,

View File

@ -38,7 +38,19 @@ export class TestSessionsComponent implements OnInit, OnDestroy {
this.eventsInfoSubscription = this.testFeedService.newLastEvent$.subscribe(
newEvent => {
(window as any).myEvents += ('<br>' + this.stringifyEventNoCircularDependencies(newEvent));
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
(window as any).myEvents += ('<br>' + JSON.stringify(newEvent, getCircularReplacer()));
});
}
@ -100,28 +112,4 @@ export class TestSessionsComponent implements OnInit, OnDestroy {
this.loadSubs(subs);
}
stringifyEventNoCircularDependencies(event: Event): string {
const cache = [];
return JSON.stringify(event, function (key, value) {
if (key !== 'ee' && key !== 'openvidu') {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Duplicate reference found
try {
// If this value does not reference a parent
return JSON.parse(JSON.stringify(value));
} catch (error) {
return;
}
}
// Store value in our collection
cache.push(value);
}
return value;
} else {
return;
}
});
}
}