From 8a2653fc05501437ce46750b4c8a25359813856b Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Sun, 6 Sep 2026 23:23:12 +0200 Subject: [PATCH] openvidu-test-e2e: bound page loads, log remote session ids and report every browser failure in parallel-browser tests --- .../openvidu/test/browsers/BrowserUser.java | 19 ++++++ .../io/openvidu/test/browsers/ChromeUser.java | 1 + .../io/openvidu/test/browsers/EdgeUser.java | 1 + .../openvidu/test/browsers/FirefoxUser.java | 1 + .../io/openvidu/test/browsers/OperaUser.java | 1 + .../e2e/AbstractOpenViduTestappE2eTest.java | 61 ++++++++++++++++++- .../test/e2e/OpenViduTestAppE2eTest.java | 43 +++++++------ 7 files changed, 105 insertions(+), 22 deletions(-) diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/BrowserUser.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/BrowserUser.java index a60626f99..1abc6e703 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/BrowserUser.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/BrowserUser.java @@ -27,7 +27,9 @@ import java.util.Map; import org.openqa.selenium.Dimension; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; import org.slf4j.LoggerFactory; @@ -68,6 +70,23 @@ public class BrowserUser { protected void configureDriver() { this.waiter = new WebDriverWait(this.driver, Duration.ofSeconds(timeOfWaitInSeconds)); + try { + // Bound page loads to the user's wait time. + this.driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(timeOfWaitInSeconds)); + } catch (WebDriverException e) { + log.warn("Could not set the page load timeout on this driver: {}", e.getMessage()); + } + } + + /** + * Logs the id of the remote session just created. This helps differentiate + * between a hang in the Grid and a hang in the page load. + */ + protected void logRemoteSessionCreated(String browser) { + if (this.driver instanceof RemoteWebDriver) { + log.info("Remote WebDriver session created [browser: {}, sessionId: {}]", browser, + ((RemoteWebDriver) this.driver).getSessionId()); + } } protected void configureDriver(Dimension windowDimensions) { diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/ChromeUser.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/ChromeUser.java index 116904ac5..24dd5d62c 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/ChromeUser.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/ChromeUser.java @@ -91,6 +91,7 @@ public class ChromeUser extends BrowserUser { log.info("Using URL {} to connect to remote web driver", REMOTE_URL); try { this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options); + logRemoteSessionCreated("chrome"); } catch (MalformedURLException e) { e.printStackTrace(); } diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/EdgeUser.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/EdgeUser.java index effecbd1f..d7d51f5d3 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/EdgeUser.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/EdgeUser.java @@ -32,6 +32,7 @@ public class EdgeUser extends BrowserUser { log.info("Using URL {} to connect to remote web driver", REMOTE_URL); try { this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options); + logRemoteSessionCreated("edge"); } catch (MalformedURLException e) { e.printStackTrace(); } diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/FirefoxUser.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/FirefoxUser.java index 5a8d2a70b..adc38b2d2 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/FirefoxUser.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/FirefoxUser.java @@ -61,6 +61,7 @@ public class FirefoxUser extends BrowserUser { log.info("Using URL {} to connect to remote web driver", REMOTE_URL); try { this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options); + logRemoteSessionCreated("firefox"); } catch (MalformedURLException e) { e.printStackTrace(); } diff --git a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/OperaUser.java b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/OperaUser.java index ab69e40f3..d4de69bd0 100644 --- a/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/OperaUser.java +++ b/openvidu-test-browsers/src/main/java/io/openvidu/test/browsers/OperaUser.java @@ -31,6 +31,7 @@ public class OperaUser extends BrowserUser { log.info("Using URL {} to connect to remote web driver", REMOTE_URL); try { this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options); + logRemoteSessionCreated("opera"); } catch (MalformedURLException e) { e.printStackTrace(); } diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/AbstractOpenViduTestappE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/AbstractOpenViduTestappE2eTest.java index 1981e7fbf..608eee1c4 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/AbstractOpenViduTestappE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/AbstractOpenViduTestappE2eTest.java @@ -1,9 +1,14 @@ package io.openvidu.test.e2e; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.AfterEach; @@ -29,14 +34,15 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e { protected Collection testappUsers = new HashSet<>(); private void connectToOpenViduTestApp(OpenViduTestappUser user) { - user.getDriver().get(APP_URL); try { + user.getDriver().get(APP_URL); user.getWaiter().until(ExpectedConditions.presenceOfElementLocated(By.id("livekit-url"))); } catch (TimeoutException e) { // Dump diagnostics and retry once with a reload before giving up String screenshot = "data:image/png;base64," + ((TakesScreenshot) user.getDriver()).getScreenshotAs(BASE64); - System.out.println("TIMEOUT WAITING FOR " + APP_URL + " TO LOAD, RETRYING ONCE. Page source:"); + System.out.println("TIMEOUT WAITING FOR " + APP_URL + " TO LOAD (" + firstLine(e.getMessage()) + + "), RETRYING ONCE. Page source:"); System.out.println(user.getDriver().getPageSource()); System.out.println(screenshot); user.getDriver().get(APP_URL); @@ -54,6 +60,57 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e { user.getEventManager().startPolling(); } + private static String firstLine(String message) { + if (message == null) { + return ""; + } + int eol = message.indexOf('\n'); + return eol == -1 ? message : message.substring(0, eol); + } + + /** + * Waits for every browser task of a parallel-browser test and, if any failed, + * fails the test reporting all the failures. + */ + protected void awaitBrowserTasks(Future... tasks) { + final int capSeconds = 300; + List failures = new ArrayList<>(); + for (int i = 0; i < tasks.length; i++) { + try { + tasks[i].get(capSeconds, TimeUnit.SECONDS); + } catch (ExecutionException e) { + Throwable cause = e.getCause() != null ? e.getCause() : e; + log.error("Browser task {} of {} failed", i + 1, tasks.length, cause); + failures.add(cause); + } catch (java.util.concurrent.TimeoutException e) { + tasks[i].cancel(true); + AssertionError stuck = new AssertionError("Browser task " + (i + 1) + " of " + tasks.length + + " did not finish within " + capSeconds + " s and was cancelled"); + log.error("Browser task {} of {} did not finish within {} s, cancelling it", i + 1, tasks.length, + capSeconds); + failures.add(stuck); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for the browser tasks", e); + } + } + if (!failures.isEmpty()) { + StringBuilder message = new StringBuilder("Error while running browsers in parallel: ") + .append(failures.size()).append(" of ").append(tasks.length).append(" browser task(s) failed"); + for (Throwable failure : failures) { + message.append("\n - ").append(failure.getClass().getSimpleName()).append(": ") + .append(firstLine(failure.getMessage())); + } + // The first failure is the cause; the others travel as suppressed exceptions so + // that every stack trace ends up in the surefire report as well + Throwable first = failures.get(0); + for (int i = 1; i < failures.size(); i++) { + first.addSuppressed(failures.get(i)); + } + Assertions.fail(message.toString(), first); + } + } + protected OpenViduTestappUser setupBrowserAndConnectToOpenViduTestapp(String browser) throws Exception { BrowserUser browserUser = this.setupBrowser(browser); OpenViduTestappUser testappUser = new OpenViduTestappUser(browserUser); diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 2a4f9b5fd..91100d3b7 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -720,7 +720,9 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { * Run the given tasks concurrently (one thread each), wait for all to finish, * then propagate the first failure — AssertionError or any exception — to the * caller's thread, so assertions inside the tasks actually fail the test (an - * AssertionError thrown in a worker thread would otherwise be lost). Each task + * AssertionError thrown in a worker thread would otherwise be lost). Every + * failure is logged and the ones after the first travel as suppressed exceptions + * of the propagated one, so no browser's error is masked by another's. Each task * MUST drive a distinct WebDriver, since a Selenium driver is not thread-safe. */ private void runInParallel(ThrowingRunnable... tasks) throws Exception { @@ -733,20 +735,31 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { return null; }); } + List failures = new ArrayList<>(); + int index = 0; for (Future future : executor.invokeAll(callables)) { + index++; try { future.get(); } catch (ExecutionException e) { Throwable cause = e.getCause() != null ? e.getCause() : e; - if (cause instanceof Error) { - throw (Error) cause; - } - if (cause instanceof Exception) { - throw (Exception) cause; - } - throw new RuntimeException(cause); + log.error("Parallel task {} of {} failed", index, tasks.length, cause); + failures.add(cause); } } + if (!failures.isEmpty()) { + Throwable cause = failures.get(0); + for (int i = 1; i < failures.size(); i++) { + cause.addSuppressed(failures.get(i)); + } + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } } finally { executor.shutdownNow(); } @@ -2180,12 +2193,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { } }); - try { - task1.get(); - task2.get(); - } catch (ExecutionException ex) { - Assertions.fail("Error while running browsers in parallel", ex); - } + awaitBrowserTasks(task1, task2); } @Test @@ -2342,12 +2350,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { } }); - try { - task1.get(); - task2.get(); - } catch (ExecutionException ex) { - Assertions.fail("Error while running browsers in parallel", ex); - } + awaitBrowserTasks(task1, task2); long pub = publisher1920AtMs.get(); long sub = subscriber1920AtMs.get();