openvidu-test-e2e: add e2e test for individual recordings abruptly stopped

v2
pabloFuente 2025-11-13 20:38:03 +01:00
parent 46050c40b4
commit 216d0e399f
5 changed files with 388 additions and 24 deletions

View File

@ -17,22 +17,41 @@
package io.openvidu.test.browsers; package io.openvidu.test.browsers;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
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.concurrent.TimeoutException;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.NoSuchSessionException;
import org.openqa.selenium.UnexpectedAlertBehaviour; import org.openqa.selenium.UnexpectedAlertBehaviour;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.chromium.HasCdp;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebDriver;
public class ChromeUser extends BrowserUser { public class ChromeUser extends BrowserUser {
private Long chromeDriverPid;
public ChromeUser(String userName, int timeOfWaitInSeconds, boolean headless) { public ChromeUser(String userName, int timeOfWaitInSeconds, boolean headless) {
this(userName, timeOfWaitInSeconds, generateDefaultScreenChromeOptions(), headless); this(userName, timeOfWaitInSeconds, generateDefaultScreenChromeOptions(), headless);
} }
@ -92,7 +111,9 @@ public class ChromeUser extends BrowserUser {
} }
} else { } else {
log.info("Using local web driver"); log.info("Using local web driver");
this.driver = new ChromeDriver(options); ChromeDriver chromeDriver = new ChromeDriver(options);
this.driver = chromeDriver;
this.chromeDriverPid = getChromeDriverPid(chromeDriver);
} }
this.driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(timeOfWaitInSeconds)); this.driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(timeOfWaitInSeconds));
@ -135,4 +156,184 @@ public class ChromeUser extends BrowserUser {
return options; return options;
} }
/**
* Simulates an abrupt Chrome crash by forcefully killing the browser process.
* Works for both local and remote WebDriver instances.
*
* @throws IOException if process killing fails
*/
public void simulateCrash() throws IOException {
String REMOTE_URL = System.getProperty("REMOTE_URL_CHROME");
if (REMOTE_URL != null) {
// Remote WebDriver: Use Chrome DevTools Protocol to crash the browser
simulateRemoteCrash();
} else {
// Local WebDriver: Kill the Chrome process directly
simulateLocalCrash();
}
log.info("Simulated Chrome crash for user {}", this.clientData);
}
/**
* Simulates crash for local Chrome instance by killing the process
*/
private void simulateLocalCrash() throws IOException {
ProcessHandle.of(chromeDriverPid).ifPresent(ph -> {
// Kill all descendant processes (the actual Chrome browser processes)
ph.descendants().forEach(child -> child.destroyForcibly());
});
}
/**
* Simulates crash for remote Chrome instance using CDP
*/
private void simulateRemoteCrash() {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
try {
Map<String, Object> params = Collections.emptyMap();
HasCdp cdp = (driver instanceof HasCdp)
? (HasCdp) driver
: (HasCdp) new Augmenter().augment(driver);
cdp.executeCdpCommand("Browser.crash", params);
} catch (Exception ignored) {
// Expected if browser crashes while executing the command
}
}, executor);
try {
cf.get(2, TimeUnit.SECONDS);
log.info("Browser crash command executed (response received)");
} catch (TimeoutException te) {
log.info("Browser crash command sent (no response as expected)");
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
log.warn("Interrupted while sending crash command", ie);
} catch (ExecutionException ee) {
log.info("Browser crashed (execution failure)", ee.getCause());
}
} catch (Exception e) {
log.warn(
"CDP crash command failed, attempting alternative method by directly killing \"selenium/standalone-chrome\" docker container",
e);
try {
Runtime.getRuntime().exec(new String[] { "sh", "-c",
"docker ps | grep selenium/standalone-chrome | awk '{print $1}' | xargs -I {} docker kill {}" });
} catch (IOException ex) {
log.error("Failed to kill remote Chrome process", ex);
}
} finally {
executor.shutdownNow();
}
}
/**
* Extracts Chrome driver process PID from ChromeDriver
*/
private Long getChromeDriverPid(ChromeDriver driver) {
try {
Capabilities caps = driver.getCapabilities();
Object debuggerAddress = caps.getCapability("goog:chromeOptions");
if (debuggerAddress instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> options = (Map<String, Object>) debuggerAddress;
Object addr = options.get("debuggerAddress");
if (addr != null) {
// Parse port from debugger address (e.g., "localhost:12345")
String[] parts = addr.toString().split(":");
if (parts.length == 2) {
int port = Integer.parseInt(parts[1]);
return findPidByPort(port);
}
}
}
// Fallback: Find Chrome process by command line
return findChromePidByCommandLine();
} catch (Exception e) {
log.warn("Failed to get Chrome PID from driver", e);
return null;
}
}
/**
* Finds Chrome PID by the port it's listening on
*/
private Long findPidByPort(int port) {
try {
String os = System.getProperty("os.name").toLowerCase();
Process process;
if (os.contains("win")) {
process = Runtime.getRuntime().exec(new String[] { "netstat", "-ano" });
} else {
process = Runtime.getRuntime().exec(new String[] { "sh", "-c", "lsof -ti:" + port + " | head -n 1" });
}
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
if (os.contains("win")) {
while ((line = reader.readLine()) != null) {
if (line.contains(":" + port + " ")) {
String[] parts = line.trim().split("\\s+");
return Long.parseLong(parts[parts.length - 1]);
}
}
} else {
line = reader.readLine();
if (line != null && !line.isEmpty()) {
return Long.parseLong(line.trim());
}
}
} catch (Exception e) {
log.warn("Failed to find PID by port", e);
}
return null;
}
/**
* Finds Chrome PID by searching for chrome process
*/
private Long findChromePidByCommandLine() {
try {
String os = System.getProperty("os.name").toLowerCase();
Process process;
if (os.contains("win")) {
process = Runtime.getRuntime()
.exec(new String[] { "sh", "-c",
"wmic process where \"name='chrome.exe'\" get ProcessId,CommandLine" });
} else if (os.contains("mac")) {
process = Runtime.getRuntime().exec(new String[] { "sh", "-c",
"ps aux | grep -i chrome | grep -v grep | awk '{print $2}' | head -n 1" });
} else {
process = Runtime.getRuntime().exec(new String[] { "sh", "-c",
"pgrep -f chrome | head -n 1" });
}
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
if (os.contains("win")) {
reader.readLine(); // Skip header
line = reader.readLine();
if (line != null) {
String[] parts = line.trim().split("\\s+");
return Long.parseLong(parts[parts.length - 1]);
}
} else {
line = reader.readLine();
if (line != null && !line.isEmpty()) {
return Long.parseLong(line.trim());
}
}
} catch (Exception e) {
log.warn("Failed to find Chrome PID by command line", e);
}
return null;
}
} }

View File

@ -47,7 +47,7 @@ public class RecordingUtils {
private boolean recordedFileFine(File file, Recording recording, private boolean recordedFileFine(File file, Recording recording,
Function<Map<String, Long>, Boolean> colorCheckFunction) throws IOException { Function<Map<String, Long>, Boolean> colorCheckFunction) throws IOException {
this.checkMultimediaFile(file, recording.hasAudio(), recording.hasVideo(), recording.getDuration(), this.checkMultimediaFile(file, recording.hasAudio(), recording.hasVideo(), recording.getDuration(),
recording.getResolution(), recording.getFrameRate(), "aac", "h264", true); recording.getResolution(), recording.getFrameRate(), "aac", "h264", true, 2);
boolean isFine = false; boolean isFine = false;
Picture frame; Picture frame;
@ -115,7 +115,8 @@ public class RecordingUtils {
} }
public void checkIndividualRecording(String recPath, Recording recording, int numberOfVideoFiles, public void checkIndividualRecording(String recPath, Recording recording, int numberOfVideoFiles,
String audioDecoder, String videoDecoder, boolean checkAudio) throws IOException { String audioDecoder, String videoDecoder, boolean checkAudio, long durationToleranceInSeconds)
throws IOException {
// Should be only 2 files: zip and metadata // Should be only 2 files: zip and metadata
File folder = new File(recPath); File folder = new File(recPath);
@ -179,7 +180,8 @@ public class RecordingUtils {
log.info("Duration of {} according to sync metadata json file: {} s", webmFile.getName(), log.info("Duration of {} according to sync metadata json file: {} s", webmFile.getName(),
durationInSeconds); durationInSeconds);
this.checkMultimediaFile(webmFile, recording.hasAudio(), recording.hasVideo(), durationInSeconds, this.checkMultimediaFile(webmFile, recording.hasAudio(), recording.hasVideo(), durationInSeconds,
recording.getResolution(), recording.getFrameRate(), audioDecoder, videoDecoder, checkAudio); recording.getResolution(), recording.getFrameRate(), audioDecoder, videoDecoder, checkAudio,
durationToleranceInSeconds);
webmFile.delete(); webmFile.delete();
} }
@ -190,7 +192,9 @@ public class RecordingUtils {
} }
public void checkMultimediaFile(File file, boolean hasAudio, boolean hasVideo, double duration, String resolution, public void checkMultimediaFile(File file, boolean hasAudio, boolean hasVideo, double duration, String resolution,
Integer frameRate, String audioDecoder, String videoDecoder, boolean checkAudio) throws IOException { Integer frameRate, String audioDecoder, String videoDecoder, boolean checkAudio,
long durationToleranceInSeconds)
throws IOException {
// Check tracks, duration, resolution, framerate and decoders // Check tracks, duration, resolution, framerate and decoders
MultimediaFileMetadata metadata = new MultimediaFileMetadata(file.getAbsolutePath()); MultimediaFileMetadata metadata = new MultimediaFileMetadata(file.getAbsolutePath());
@ -225,11 +229,11 @@ public class RecordingUtils {
df.setRoundingMode(RoundingMode.UP); df.setRoundingMode(RoundingMode.UP);
log.info("Duration of {} according to ffmpeg: {} s", file.getName(), metadata.getDuration()); log.info("Duration of {} according to ffmpeg: {} s", file.getName(), metadata.getDuration());
log.info("Duration of {} according to 'duration' property: {} s", file.getName(), duration); log.info("Duration of {} according to 'duration' property: {} s", file.getName(), duration);
log.info("Difference in s duration: {}", Math.abs(metadata.getDuration() - duration)); log.info("Difference in s duration: {}", Math.abs(duration - metadata.getDuration()));
final double difference = 10; Assertions.assertTrue(Math.abs((duration - metadata.getDuration())) < durationToleranceInSeconds,
Assertions.assertTrue(Math.abs((metadata.getDuration() - duration)) < difference,
"Difference between recording entity duration (" + duration + ") and real video duration (" "Difference between recording entity duration (" + duration + ") and real video duration ("
+ metadata.getDuration() + ") is greater than " + difference + " in file " + file.getName()); + metadata.getDuration() + ") is greater than " + durationToleranceInSeconds + " in file "
+ file.getName());
} }
public boolean thumbnailIsFine(File file, Function<Map<String, Long>, Boolean> colorCheckFunction) { public boolean thumbnailIsFine(File file, Function<Map<String, Long>, Boolean> colorCheckFunction) {

View File

@ -45,7 +45,7 @@ public class MediaNodeDockerUtils {
DockerClient dockerClient = getDockerClient(); DockerClient dockerClient = getDockerClient();
ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true) ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true)
.withAttachStderr(true) .withAttachStderr(true)
.withCmd("bash", "-c", "docker stop kms && sleep " + (millisStop / 1000) + " && docker start kms") .withCmd("bash", "-c", "docker kill kms && sleep " + (millisStop / 1000) + " && docker start kms")
.exec(); .exec();
dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ResultCallback.Adapter<>() { dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ResultCallback.Adapter<>() {
}); });

View File

@ -58,12 +58,14 @@ import io.openvidu.java.client.OpenViduRole;
import io.openvidu.java.client.Recording; import io.openvidu.java.client.Recording;
import io.openvidu.java.client.RecordingProperties; import io.openvidu.java.client.RecordingProperties;
import io.openvidu.java.client.Session; import io.openvidu.java.client.Session;
import io.openvidu.test.browsers.ChromeUser;
import io.openvidu.test.browsers.utils.CustomHttpClient; import io.openvidu.test.browsers.utils.CustomHttpClient;
import io.openvidu.test.browsers.utils.RecordingUtils; import io.openvidu.test.browsers.utils.RecordingUtils;
import io.openvidu.test.browsers.utils.Unzipper; import io.openvidu.test.browsers.utils.Unzipper;
import io.openvidu.test.browsers.utils.layout.CustomLayoutHandler; import io.openvidu.test.browsers.utils.layout.CustomLayoutHandler;
import io.openvidu.test.browsers.utils.webhook.CustomWebhook; import io.openvidu.test.browsers.utils.webhook.CustomWebhook;
import io.openvidu.test.e2e.utils.TestUtils; import io.openvidu.test.e2e.utils.TestUtils;
import io.openvidu.test.e2e.annotations.OnlyKurento;
public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest { public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@ -649,10 +651,13 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
} }
} }
long accumulatedTimesWhenSartingRecordings = 0;
// Start the recording of the sessions // Start the recording of the sessions
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start", restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/start",
"{'session':'" + sessionName + "','outputMode':'INDIVIDUAL'}", HttpURLConnection.HTTP_OK); "{'session':'" + sessionName + "','outputMode':'INDIVIDUAL'}", HttpURLConnection.HTTP_OK);
user.getEventManager().waitUntilEventReaches("recordingStarted", 3); user.getEventManager().waitUntilEventReaches("recordingStarted", 3);
Thread.sleep(1000); Thread.sleep(1000);
// Get connectionId and streamId for one of the users configured to NOT be // Get connectionId and streamId for one of the users configured to NOT be
@ -671,20 +676,39 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
} }
} }
long timeBeforeOp = System.currentTimeMillis();
// Generate 3 total recordings of 1 second length for the stream of the user // Generate 3 total recordings of 1 second length for the stream of the user
// configured to NOT be recorded // configured to NOT be recorded
restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2, restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2,
"{'record':true}", HttpURLConnection.HTTP_OK); "{'record':true}", HttpURLConnection.HTTP_OK);
user.getEventManager().waitUntilEventReaches("connectionPropertyChanged", 1);
accumulatedTimesWhenSartingRecordings += System.currentTimeMillis() - timeBeforeOp;
Thread.sleep(1000); Thread.sleep(1000);
timeBeforeOp = System.currentTimeMillis();
restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2, restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2,
"{'record':false}", HttpURLConnection.HTTP_OK); "{'record':false}", HttpURLConnection.HTTP_OK);
restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2, restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2,
"{'record':true}", HttpURLConnection.HTTP_OK); "{'record':true}", HttpURLConnection.HTTP_OK);
user.getEventManager().waitUntilEventReaches("connectionPropertyChanged", 3);
accumulatedTimesWhenSartingRecordings += System.currentTimeMillis() - timeBeforeOp;
Thread.sleep(1000); Thread.sleep(1000);
timeBeforeOp = System.currentTimeMillis();
restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2, restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2,
"{'record':false}", HttpURLConnection.HTTP_OK); "{'record':false}", HttpURLConnection.HTTP_OK);
restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2, restClient.rest(HttpMethod.PATCH, "/openvidu/api/sessions/" + sessionName + "/connection/" + connectionId2,
"{'record':true}", HttpURLConnection.HTTP_OK); "{'record':true}", HttpURLConnection.HTTP_OK);
user.getEventManager().waitUntilEventReaches("connectionPropertyChanged", 5);
accumulatedTimesWhenSartingRecordings += System.currentTimeMillis() - timeBeforeOp;
Thread.sleep(1000); Thread.sleep(1000);
restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/stop/" + sessionName, HttpURLConnection.HTTP_OK); restClient.rest(HttpMethod.POST, "/openvidu/api/recordings/stop/" + sessionName, HttpURLConnection.HTTP_OK);
@ -695,7 +719,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
String recPath = "/opt/openvidu/recordings/" + sessionName + "/"; String recPath = "/opt/openvidu/recordings/" + sessionName + "/";
Recording recording = new OpenVidu(OpenViduTestAppE2eTest.OPENVIDU_URL, OpenViduTestAppE2eTest.OPENVIDU_SECRET) Recording recording = new OpenVidu(OpenViduTestAppE2eTest.OPENVIDU_URL, OpenViduTestAppE2eTest.OPENVIDU_SECRET)
.getRecording(sessionName); .getRecording(sessionName);
this.recordingUtils.checkIndividualRecording(recPath, recording, 4, "opus", "vp8", true); this.recordingUtils.checkIndividualRecording(recPath, recording, 4, "opus", "vp8", true, 5);
// Analyze INDIVIDUAL recording metadata // Analyze INDIVIDUAL recording metadata
new Unzipper().unzipFile(recPath, recording.getName() + ".zip"); new Unzipper().unzipFile(recPath, recording.getName() + ".zip");
@ -718,8 +742,9 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assertions.assertEquals(connectionId1, file.get("connectionId").getAsString(), Assertions.assertEquals(connectionId1, file.get("connectionId").getAsString(),
"Wrong connectionId file metadata property"); "Wrong connectionId file metadata property");
long msDuration = file.get("endTimeOffset").getAsLong() - file.get("startTimeOffset").getAsLong(); long msDuration = file.get("endTimeOffset").getAsLong() - file.get("startTimeOffset").getAsLong();
Assertions.assertTrue((msDuration - 4000) < 1000, long differenceInDuration = msDuration - 4000 - accumulatedTimesWhenSartingRecordings;
"Wrong recording duration of individual file. Difference: " + (msDuration - 4000)); Assertions.assertTrue(differenceInDuration < 750,
"Wrong recording duration of individual file. Difference: " + differenceInDuration);
count1++; count1++;
} else if (fileStreamId.equals(streamId2)) { } else if (fileStreamId.equals(streamId2)) {
// Dynamically recorded user // Dynamically recorded user
@ -753,6 +778,137 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assertions.assertTrue(regexNames.isEmpty(), "Some expected file name didn't existed: " + regexNames.toString()); Assertions.assertTrue(regexNames.isEmpty(), "Some expected file name didn't existed: " + regexNames.toString());
} }
@Test
@DisplayName("Individual record with abrupt user disconnection")
void individualRecordWithAbruptUserDisconnectionTest() throws Exception {
isRecordingTest = true;
log.info("Individual record with abrupt user disconnection");
CustomHttpClient restClient = new CustomHttpClient(OpenViduTestAppE2eTest.OPENVIDU_URL, "OPENVIDUAPP",
OpenViduTestAppE2eTest.OPENVIDU_SECRET);
JsonObject config = restClient.rest(HttpMethod.GET, "/openvidu/api/config", HttpURLConnection.HTTP_OK);
String defaultOpenViduWebhookEndpoint = null;
Integer defaultOpenViduRecordingAutostopTimeout = null;
if (config.has("OPENVIDU_WEBHOOK_ENDPOINT")) {
defaultOpenViduWebhookEndpoint = config.get("OPENVIDU_WEBHOOK_ENDPOINT").getAsString();
}
if (config.has("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT")) {
defaultOpenViduRecordingAutostopTimeout = config.get("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT").getAsInt();
}
CountDownLatch initLatch = new CountDownLatch(1);
io.openvidu.test.browsers.utils.webhook.CustomWebhook.main(new String[0], initLatch);
try {
if (!initLatch.await(30, TimeUnit.SECONDS)) {
Assertions.fail("Timeout waiting for webhook springboot app to start");
CustomWebhook.shutDown();
return;
}
Map<String, Object> newConfig = Map.of("OPENVIDU_PRO_NETWORK_QUALITY", false,
"OPENVIDU_PRO_SPEECH_TO_TEXT", "disabled", "OPENVIDU_WEBHOOK", true,
"OPENVIDU_WEBHOOK_ENDPOINT", "http://127.0.0.1:7777/webhook",
"OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", 0);
restartOpenViduServer(newConfig);
OpenViduTestappUser user = setupBrowserAndConnectToOpenViduTestapp("chrome");
final String sessionName = "AbruptStopOfIndividualRecording";
final String recordingName = "ABRUPT_STOP_OF_INDIVIDUAL_RECORDING";
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.id("session-name-input-0")).clear();
user.getDriver().findElement(By.id("session-name-input-0")).sendKeys(sessionName);
user.getDriver().findElement(By.className("join-btn")).click();
user.getEventManager().waitUntilEventReaches("connectionCreated", 1);
user.getEventManager().waitUntilEventReaches("accessAllowed", 1);
user.getEventManager().waitUntilEventReaches("streamCreated", 1);
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
int numberOfVideos = user.getDriver().findElements(By.tagName("video")).size();
Assertions.assertEquals(1, numberOfVideos, "Expected 1 video but found " + numberOfVideos);
Assertions.assertTrue(
user.getBrowserUser().assertMediaTracks(user.getDriver().findElements(By.tagName("video")), true,
true),
"Video was expected to have audio and video tracks");
user.getDriver().findElement(By.id("session-api-btn-0")).click();
Thread.sleep(1000);
user.getDriver().findElement(By.id("rec-properties-btn")).click();
Thread.sleep(500);
// Set recording name
user.getDriver().findElement(By.id("recording-name-field")).sendKeys(recordingName);
// Set OutputMode to INDIVIDUAL
user.getDriver().findElement(By.id("rec-outputmode-select")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("option-INDIVIDUAL")).click();
Thread.sleep(500);
user.getDriver().findElement(By.id("start-recording-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeToBe(By.id("api-response-text-area"), "value",
"Recording started [" + sessionName + "]"));
user.getEventManager().waitUntilEventReaches("recordingStarted", 1);
Thread.sleep(3000);
CustomWebhook.clean();
((ChromeUser) user.getBrowserUser()).simulateCrash();
JsonObject event = CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 20);
Assertions.assertEquals("networkDisconnect", event.get("reason").getAsString(),
"Wrong reason for webrtcConnectionDestroyed event");
event = CustomWebhook.waitForEvent("participantLeft", 1);
Assertions.assertEquals("networkDisconnect", event.get("reason").getAsString(),
"Wrong reason for participantLeft event");
event = CustomWebhook.waitForEvent("recordingStatusChanged", 1);
Assertions.assertEquals("lastParticipantLeft", event.get("reason").getAsString(),
"Wrong reason for recordingStatusChanged event");
Assertions.assertEquals("stopped", event.get("status").getAsString(),
"Wrong status in recordingStatusChanged event");
event = CustomWebhook.waitForEvent("sessionDestroyed", 1);
Assertions.assertEquals("lastParticipantLeft", event.get("reason").getAsString(),
"Wrong reason for sessionDestroyed event");
event = CustomWebhook.waitForEvent("recordingStatusChanged", 1);
Assertions.assertEquals("lastParticipantLeft", event.get("reason").getAsString(),
"Wrong reason for recordingStatusChanged event");
Assertions.assertEquals("ready", event.get("status").getAsString(),
"Wrong status in recordingStatusChanged event");
// Check that the INDIVIDUAL recording has been properly saved and is healthy
// even after Chrome crash
String recPath = "/opt/openvidu/recordings/" + sessionName + "/";
Recording recording = new OpenVidu(OpenViduTestAppE2eTest.OPENVIDU_URL,
OpenViduTestAppE2eTest.OPENVIDU_SECRET)
.getRecording(sessionName);
this.recordingUtils.checkIndividualRecording(recPath, recording, 1, "opus", "vp8", true, 15);
} finally {
Map<String, Object> oldConfig = new HashMap<>();
oldConfig.put("OPENVIDU_WEBHOOK", false);
if (defaultOpenViduWebhookEndpoint != null) {
oldConfig.put("OPENVIDU_WEBHOOK_ENDPOINT", defaultOpenViduWebhookEndpoint);
}
if (defaultOpenViduRecordingAutostopTimeout != null) {
oldConfig.put("OPENVIDU_RECORDING_AUTOSTOP_TIMEOUT", defaultOpenViduRecordingAutostopTimeout);
}
restartOpenViduServer(oldConfig);
CustomWebhook.shutDown();
}
}
@Test @Test
@DisplayName("REST API PRO test") @DisplayName("REST API PRO test")
void restApiProTest() throws Exception { void restApiProTest() throws Exception {
@ -1143,6 +1299,7 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
} }
@Test @Test
@OnlyKurento
@DisplayName("Network quality test") @DisplayName("Network quality test")
void networkQualityTest() throws Exception { void networkQualityTest() throws Exception {
@ -1290,7 +1447,8 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
filterOptionsInput = user.getDriver().findElement(By.id("filter-options-field")); filterOptionsInput = user.getDriver().findElement(By.id("filter-options-field"));
filterOptionsInput.clear(); filterOptionsInput.clear();
filterOptionsInput.sendKeys("{\"url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Solid_red.svg/1024px-Solid_red.svg.png\"}"); filterOptionsInput.sendKeys(
"{\"url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/Solid_red.svg/1024px-Solid_red.svg.png\"}");
user.getDriver().findElement(By.id("apply-filter-btn")).click(); user.getDriver().findElement(By.id("apply-filter-btn")).click();
user.getWaiter().until( user.getWaiter().until(
ExpectedConditions.attributeContains(By.id("operation-response-text-area"), "value", "Filter applied")); ExpectedConditions.attributeContains(By.id("operation-response-text-area"), "value", "Filter applied"));
@ -1326,7 +1484,8 @@ public class OpenViduProTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
// Blue // Blue
filterParamsInput.clear(); filterParamsInput.clear();
filterParamsInput.sendKeys("{\"url\": \"https://png.pngtree.com/thumb_back/fw800/background/20210207/pngtree-blue-pure-color-simple-background-image_557085.jpg\"}"); filterParamsInput.sendKeys(
"{\"url\": \"https://png.pngtree.com/thumb_back/fw800/background/20210207/pngtree-blue-pure-color-simple-background-image_557085.jpg\"}");
user.getDriver().findElement(By.id("exec-filter-btn")).click(); user.getDriver().findElement(By.id("exec-filter-btn")).click();
user.getWaiter().until(ExpectedConditions.attributeContains(By.id("operation-response-text-area"), "value", user.getWaiter().until(ExpectedConditions.attributeContains(By.id("operation-response-text-area"), "value",
"Filter method executed")); "Filter method executed"));

View File

@ -1646,7 +1646,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
String recPath = recordingsPath + sessionName + "/"; String recPath = recordingsPath + sessionName + "/";
Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName); Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(sessionName);
this.recordingUtils.checkIndividualRecording(recPath, recording, 2, "opus", "vp8", true); this.recordingUtils.checkIndividualRecording(recPath, recording, 2, "opus", "vp8", true, 2);
// Try to get the stopped recording // Try to get the stopped recording
user.getDriver().findElement(By.id("get-recording-btn")).click(); user.getDriver().findElement(By.id("get-recording-btn")).click();
@ -1853,17 +1853,17 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
String recPath = recordingsPath + SESSION_NAME + "/"; String recPath = recordingsPath + SESSION_NAME + "/";
Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME); Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME);
this.recordingUtils.checkMultimediaFile(new File(recPath + recording.getName() + ".mp4"), false, true, this.recordingUtils.checkMultimediaFile(new File(recPath + recording.getName() + ".mp4"), false, true,
recording.getDuration(), recording.getResolution(), recording.getFrameRate(), null, "h264", true); recording.getDuration(), recording.getResolution(), recording.getFrameRate(), null, "h264", true, 2);
// Check video-only INDIVIDUAL recording // Check video-only INDIVIDUAL recording
recPath = recordingsPath + SESSION_NAME + "~1/"; recPath = recordingsPath + SESSION_NAME + "~1/";
recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME + "~1"); recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME + "~1");
this.recordingUtils.checkIndividualRecording(recPath, recording, 3, "opus", "vp8", true); this.recordingUtils.checkIndividualRecording(recPath, recording, 3, "opus", "vp8", true, 2);
// Check audio-only INDIVIDUAL recording // Check audio-only INDIVIDUAL recording
recPath = recordingsPath + SESSION_NAME + "~2/"; recPath = recordingsPath + SESSION_NAME + "~2/";
recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME + "~2"); recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME + "~2");
this.recordingUtils.checkIndividualRecording(recPath, recording, 2, "opus", "vp8", true); this.recordingUtils.checkIndividualRecording(recPath, recording, 2, "opus", "vp8", true, 2);
user.getDriver().findElement(By.id("close-dialog-btn")).click(); user.getDriver().findElement(By.id("close-dialog-btn")).click();
Thread.sleep(500); Thread.sleep(500);
@ -1945,7 +1945,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
// Check audio-only COMPOSED recording // Check audio-only COMPOSED recording
Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME); Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(SESSION_NAME);
this.recordingUtils.checkMultimediaFile(new File(recordingFilePath), true, false, recording.getDuration(), null, this.recordingUtils.checkMultimediaFile(new File(recordingFilePath), true, false, recording.getDuration(), null,
null, "opus", null, true); null, "opus", null, true, 2);
user.getDriver().findElement(By.id("close-dialog-btn")).click(); user.getDriver().findElement(By.id("close-dialog-btn")).click();
Thread.sleep(500); Thread.sleep(500);
@ -2970,7 +2970,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assertions.assertFalse(OV.fetch(), "OpenVidu.fetch() should return false"); Assertions.assertFalse(OV.fetch(), "OpenVidu.fetch() should return false");
this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/" + customSessionId + "/", recording, 2, this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/" + customSessionId + "/", recording, 2,
"opus", "vp8", false); "opus", "vp8", false, 2);
// Not recorded session // Not recorded session
try { try {
@ -3991,7 +3991,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assertions.assertTrue(rec.getSize() > 0, "Recording size is 0"); Assertions.assertTrue(rec.getSize() > 0, "Recording size is 0");
this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/TestSession/", rec, 1, "opus", "vp8", this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/TestSession/", rec, 1, "opus", "vp8",
true); true, 10);
user.getDriver().findElement(By.id("remove-all-users-btn")).click(); user.getDriver().findElement(By.id("remove-all-users-btn")).click();
user.getEventManager().clearAllCurrentEvents(); user.getEventManager().clearAllCurrentEvents();
@ -4075,7 +4075,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
Assertions.assertTrue(rec.getSize() > 0, "Recording size is 0"); Assertions.assertTrue(rec.getSize() > 0, "Recording size is 0");
this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/TestSession/", rec, 1, "opus", "vp8", this.recordingUtils.checkIndividualRecording("/opt/openvidu/recordings/TestSession/", rec, 1, "opus", "vp8",
true); true, 10);
OV.fetch(); OV.fetch();
sessions = OV.getActiveSessions(); sessions = OV.getActiveSessions();
@ -4569,7 +4569,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
CustomWebhook.waitForEvent("recordingStatusChanged", 10); // Ready CustomWebhook.waitForEvent("recordingStatusChanged", 10); // Ready
Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(recId); Recording recording = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET).getRecording(recId);
this.recordingUtils.checkIndividualRecording(recPath + recId + "/", recording, 1, "opus", "vp8", true); this.recordingUtils.checkIndividualRecording(recPath + recId + "/", recording, 1, "opus", "vp8", true, 2);
// Test IPCAM individual recording (IPCAM video only, recording audio and video) // Test IPCAM individual recording (IPCAM video only, recording audio and video)