mirror of https://github.com/OpenVidu/openvidu.git
openvidu-test-e2e: bound page loads, log remote session ids and report every browser failure in parallel-browser tests
parent
2fc78cbfb2
commit
8a2653fc05
|
|
@ -27,7 +27,9 @@ import java.util.Map;
|
||||||
import org.openqa.selenium.Dimension;
|
import org.openqa.selenium.Dimension;
|
||||||
import org.openqa.selenium.JavascriptExecutor;
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
import org.openqa.selenium.WebDriverException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||||
import org.openqa.selenium.support.ui.ExpectedCondition;
|
import org.openqa.selenium.support.ui.ExpectedCondition;
|
||||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -68,6 +70,23 @@ public class BrowserUser {
|
||||||
|
|
||||||
protected void configureDriver() {
|
protected void configureDriver() {
|
||||||
this.waiter = new WebDriverWait(this.driver, Duration.ofSeconds(timeOfWaitInSeconds));
|
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) {
|
protected void configureDriver(Dimension windowDimensions) {
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ public class ChromeUser extends BrowserUser {
|
||||||
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
||||||
try {
|
try {
|
||||||
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
||||||
|
logRemoteSessionCreated("chrome");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ public class EdgeUser extends BrowserUser {
|
||||||
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
||||||
try {
|
try {
|
||||||
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
||||||
|
logRemoteSessionCreated("edge");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ public class FirefoxUser extends BrowserUser {
|
||||||
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
||||||
try {
|
try {
|
||||||
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
||||||
|
logRemoteSessionCreated("firefox");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ public class OperaUser extends BrowserUser {
|
||||||
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
log.info("Using URL {} to connect to remote web driver", REMOTE_URL);
|
||||||
try {
|
try {
|
||||||
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
this.driver = new RemoteWebDriver(new URL(REMOTE_URL), options);
|
||||||
|
logRemoteSessionCreated("opera");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
package io.openvidu.test.e2e;
|
package io.openvidu.test.e2e;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
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 java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
|
@ -29,14 +34,15 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e {
|
||||||
protected Collection<OpenViduTestappUser> testappUsers = new HashSet<>();
|
protected Collection<OpenViduTestappUser> testappUsers = new HashSet<>();
|
||||||
|
|
||||||
private void connectToOpenViduTestApp(OpenViduTestappUser user) {
|
private void connectToOpenViduTestApp(OpenViduTestappUser user) {
|
||||||
user.getDriver().get(APP_URL);
|
|
||||||
try {
|
try {
|
||||||
|
user.getDriver().get(APP_URL);
|
||||||
user.getWaiter().until(ExpectedConditions.presenceOfElementLocated(By.id("livekit-url")));
|
user.getWaiter().until(ExpectedConditions.presenceOfElementLocated(By.id("livekit-url")));
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// Dump diagnostics and retry once with a reload before giving up
|
// Dump diagnostics and retry once with a reload before giving up
|
||||||
String screenshot = "data:image/png;base64,"
|
String screenshot = "data:image/png;base64,"
|
||||||
+ ((TakesScreenshot) user.getDriver()).getScreenshotAs(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(user.getDriver().getPageSource());
|
||||||
System.out.println(screenshot);
|
System.out.println(screenshot);
|
||||||
user.getDriver().get(APP_URL);
|
user.getDriver().get(APP_URL);
|
||||||
|
|
@ -54,6 +60,57 @@ public class AbstractOpenViduTestappE2eTest extends OpenViduTestE2e {
|
||||||
user.getEventManager().startPolling();
|
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<Throwable> 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 {
|
protected OpenViduTestappUser setupBrowserAndConnectToOpenViduTestapp(String browser) throws Exception {
|
||||||
BrowserUser browserUser = this.setupBrowser(browser);
|
BrowserUser browserUser = this.setupBrowser(browser);
|
||||||
OpenViduTestappUser testappUser = new OpenViduTestappUser(browserUser);
|
OpenViduTestappUser testappUser = new OpenViduTestappUser(browserUser);
|
||||||
|
|
|
||||||
|
|
@ -720,7 +720,9 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
* Run the given tasks concurrently (one thread each), wait for all to finish,
|
* Run the given tasks concurrently (one thread each), wait for all to finish,
|
||||||
* then propagate the first failure — AssertionError or any exception — to the
|
* then propagate the first failure — AssertionError or any exception — to the
|
||||||
* caller's thread, so assertions inside the tasks actually fail the test (an
|
* 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.
|
* MUST drive a distinct WebDriver, since a Selenium driver is not thread-safe.
|
||||||
*/
|
*/
|
||||||
private void runInParallel(ThrowingRunnable... tasks) throws Exception {
|
private void runInParallel(ThrowingRunnable... tasks) throws Exception {
|
||||||
|
|
@ -733,20 +735,31 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
List<Throwable> failures = new ArrayList<>();
|
||||||
|
int index = 0;
|
||||||
for (Future<Void> future : executor.invokeAll(callables)) {
|
for (Future<Void> future : executor.invokeAll(callables)) {
|
||||||
|
index++;
|
||||||
try {
|
try {
|
||||||
future.get();
|
future.get();
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
||||||
if (cause instanceof Error) {
|
log.error("Parallel task {} of {} failed", index, tasks.length, cause);
|
||||||
throw (Error) cause;
|
failures.add(cause);
|
||||||
}
|
|
||||||
if (cause instanceof Exception) {
|
|
||||||
throw (Exception) cause;
|
|
||||||
}
|
|
||||||
throw new RuntimeException(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 {
|
} finally {
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
@ -2180,12 +2193,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
awaitBrowserTasks(task1, task2);
|
||||||
task1.get();
|
|
||||||
task2.get();
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
Assertions.fail("Error while running browsers in parallel", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -2342,12 +2350,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
awaitBrowserTasks(task1, task2);
|
||||||
task1.get();
|
|
||||||
task2.get();
|
|
||||||
} catch (ExecutionException ex) {
|
|
||||||
Assertions.fail("Error while running browsers in parallel", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
long pub = publisher1920AtMs.get();
|
long pub = publisher1920AtMs.get();
|
||||||
long sub = subscriber1920AtMs.get();
|
long sub = subscriber1920AtMs.get();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue