mirror of https://github.com/OpenVidu/openvidu.git
openvidu-test-browsers: wait for the remote WebDriver endpoint to report ready and retry the session once on connection errors
parent
a06aea54c4
commit
fa017e3341
|
|
@ -18,14 +18,25 @@
|
||||||
package io.openvidu.test.browsers;
|
package io.openvidu.test.browsers;
|
||||||
|
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.openqa.selenium.Capabilities;
|
||||||
import org.openqa.selenium.Dimension;
|
import org.openqa.selenium.Dimension;
|
||||||
import org.openqa.selenium.JavascriptExecutor;
|
import org.openqa.selenium.JavascriptExecutor;
|
||||||
|
import org.openqa.selenium.SessionNotCreatedException;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.openqa.selenium.WebDriverException;
|
import org.openqa.selenium.WebDriverException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
@ -78,6 +89,106 @@ public class BrowserUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long a remote WebDriver endpoint may take to report itself ready before
|
||||||
|
* the browser setup gives up with an explicit error.
|
||||||
|
*/
|
||||||
|
private static final Duration REMOTE_READY_TIMEOUT = Duration.ofSeconds(60);
|
||||||
|
|
||||||
|
private static final Pattern REMOTE_READY = Pattern.compile("\"ready\"\\s*:\\s*true");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the remote session once the endpoint reports ready, retrying once if
|
||||||
|
* the first attempt dies on a connection error.
|
||||||
|
*/
|
||||||
|
protected static RemoteWebDriver newRemoteWebDriver(URL remoteUrl, Capabilities capabilities, String browser) {
|
||||||
|
waitForRemoteWebDriverReady(remoteUrl, browser);
|
||||||
|
try {
|
||||||
|
return new RemoteWebDriver(remoteUrl, capabilities);
|
||||||
|
} catch (SessionNotCreatedException e) {
|
||||||
|
Throwable connectionError = connectionErrorOf(e);
|
||||||
|
if (connectionError == null) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.warn("Creating the remote {} session at {} failed on a connection error ({}: {}), retrying once",
|
||||||
|
browser, remoteUrl, connectionError.getClass().getSimpleName(), connectionError.getMessage());
|
||||||
|
sleepQuietly(Duration.ofSeconds(4));
|
||||||
|
waitForRemoteWebDriverReady(remoteUrl, browser);
|
||||||
|
return new RemoteWebDriver(remoteUrl, capabilities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polls the endpoint's {@code status} resource until it answers HTTP 200 with
|
||||||
|
* {@code "ready": true}, or fails after {@link #REMOTE_READY_TIMEOUT} quoting
|
||||||
|
* the last answer.
|
||||||
|
*/
|
||||||
|
protected static void waitForRemoteWebDriverReady(URL remoteUrl, String browser) {
|
||||||
|
String base = remoteUrl.toString();
|
||||||
|
String statusUrl = base.endsWith("/") ? base + "status" : base + "/status";
|
||||||
|
Instant start = Instant.now();
|
||||||
|
Instant deadline = start.plus(REMOTE_READY_TIMEOUT);
|
||||||
|
String lastAnswer = "no answer yet";
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(statusUrl).openConnection();
|
||||||
|
connection.setConnectTimeout(2000);
|
||||||
|
connection.setReadTimeout(2000);
|
||||||
|
int code = connection.getResponseCode();
|
||||||
|
String body = readBody(connection, code);
|
||||||
|
if (code == 200 && REMOTE_READY.matcher(body).find()) {
|
||||||
|
long waitedMs = Duration.between(start, Instant.now()).toMillis();
|
||||||
|
if (waitedMs > 1000) {
|
||||||
|
log.info("Remote {} WebDriver endpoint {} became ready after {} ms", browser, statusUrl,
|
||||||
|
waitedMs);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastAnswer = "HTTP " + code + " " + body.replaceAll("\\s+", " ").trim();
|
||||||
|
} catch (IOException e) {
|
||||||
|
lastAnswer = e.getClass().getSimpleName() + ": " + e.getMessage();
|
||||||
|
}
|
||||||
|
if (Instant.now().isAfter(deadline)) {
|
||||||
|
throw new IllegalStateException("Remote " + browser + " WebDriver endpoint " + statusUrl
|
||||||
|
+ " did not report ready within " + REMOTE_READY_TIMEOUT.toSeconds() + " s (last answer: "
|
||||||
|
+ lastAnswer + ")");
|
||||||
|
}
|
||||||
|
sleepQuietly(Duration.ofMillis(250));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readBody(HttpURLConnection connection, int code) throws IOException {
|
||||||
|
try (InputStream in = code >= 400 ? connection.getErrorStream() : connection.getInputStream()) {
|
||||||
|
if (in == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
byte[] bytes = in.readNBytes(4096);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection-level cause of a failed session creation
|
||||||
|
*/
|
||||||
|
private static Throwable connectionErrorOf(Throwable error) {
|
||||||
|
for (Throwable t = error; t != null; t = t.getCause()) {
|
||||||
|
String name = t.getClass().getSimpleName();
|
||||||
|
if (t instanceof ConnectException || t instanceof ClosedChannelException
|
||||||
|
|| "ConnectionException".equals(name) || "HttpConnectTimeoutException".equals(name)) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sleepQuietly(Duration duration) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(duration.toMillis());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the id of the remote session just created. This helps differentiate
|
* 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.
|
* between a hang in the Grid and a hang in the page load.
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public class ChromeUser extends BrowserUser {
|
||||||
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
||||||
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 = newRemoteWebDriver(new URL(REMOTE_URL), options, "chrome");
|
||||||
logRemoteSessionCreated("chrome");
|
logRemoteSessionCreated("chrome");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class EdgeUser extends BrowserUser {
|
||||||
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
||||||
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 = newRemoteWebDriver(new URL(REMOTE_URL), options, "edge");
|
||||||
logRemoteSessionCreated("edge");
|
logRemoteSessionCreated("edge");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class FirefoxUser extends BrowserUser {
|
||||||
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
if (REMOTE_URL != null && !REMOTE_URL.isBlank()) {
|
||||||
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 = newRemoteWebDriver(new URL(REMOTE_URL), options, "firefox");
|
||||||
logRemoteSessionCreated("firefox");
|
logRemoteSessionCreated("firefox");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ public class OperaUser extends BrowserUser {
|
||||||
if (REMOTE_URL != null) {
|
if (REMOTE_URL != null) {
|
||||||
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 = newRemoteWebDriver(new URL(REMOTE_URL), options, "opera");
|
||||||
logRemoteSessionCreated("opera");
|
logRemoteSessionCreated("opera");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue