Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM selenium/hub:3.14 AS base

FROM gradle:5.4.1-jdk8 AS gradle

RUN apt update && apt install dos2unix
COPY . .
RUN dos2unix gradlew
RUN ./gradlew clean jar

FROM base AS selenium

RUN sudo sed -i 's/\-jar \/opt\/selenium\/selenium-server-standalone\.jar'\
'/ -cp \/opt\/selenium\/selenium-server-standalone\.jar:\/opt\/selenium\/selenium-api.jar org.openqa.grid.selenium.GridLauncherV3 -servlets com.xing.qa.selenium.grid.hub.Console/'\
/opt/bin/start-selenium-hub.sh
COPY --from=gradle /home/gradle/build/libs/*.jar /opt/selenium/selenium-api.jar
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,12 @@ you can map your matcher to a capability (name) in the Matcher configuration.
* The configuration is somewhat elaborate to avoid messing with the core selenium configuration. It might be an idea to
add a configuration file on its own or to extend the selenium node configuration as the configuration by environment
might hit a limit at some point (esp. with the ConfigurableCapabilityMatcher).

## Docker / Kubernetes

Build and run in Docker. (Removes the need for JDK/JRE/Gradle etc.)
```Bash
docker build . -t selenium-api
docker run -it -p 4444:4444 selenium-api
```
A precompiled image is available on [Dock Hub](https://hub.docker.com/r/joes88/selenium-api)
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies {
provided 'org.seleniumhq.selenium:selenium-server:3.14.0'
compile 'org.json:json:20080701'
testCompile 'org.testng:testng:6.8.8'
compile 'org.influxdb:influxdb-java:2.11'
}

jar {
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/com/xing/qa/selenium/grid/hub/Console.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.openqa.grid.web.servlet.RegistryBasedServlet;
import org.openqa.selenium.BuildInfo;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Proxy;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -109,12 +110,14 @@ protected JSONObject status()
Hub h = getRegistry().getHub();

List<JSONObject> nodes = new ArrayList<>();

for (RemoteProxy proxy : getRegistry().getAllProxies()) {
try {
JSONRenderer beta = new WebProxyJsonRenderer(proxy);
nodes.add(beta.render());
} catch (Exception e) {}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}

status.put("version", coreVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public JSONObject render() throws JSONException {
}

json.put("os", value.get("os"));
json.put("java", java.get("version"));
json.put("java", java == null ? "none" : java.get("version"));
json.put("configuration", proxy.getConfig());

SlotsLines rcLines = new SlotsLines();
Expand Down Expand Up @@ -86,7 +86,9 @@ private JSONObject render(TestSlot slot) throws JSONException {
json.put("capabilities", slot.getCapabilities());
TestSession session = slot.getSession();

if (session != null) {
if (session != null
&& session.getExternalKey() != null // Session creation failed https://github.com/SeleniumHQ/selenium/blob/49dc495acaf53bb5f612e2e1552dc5dc40d1c62f/java/server/src/org/openqa/grid/internal/ActiveTestSessions.java#L77
) {
json.put("session", render(session));
json.put("busy", true);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.xing.qa.selenium.grid.node;

import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.openqa.grid.internal.ExternalSessionKey;
import org.openqa.grid.internal.TestSession;

import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

/**
* BaseSeleniumReporter
*
* @author Jens Hausherr (jens.hausherr@xing.com)
*/
public abstract class BaseSeleniumReporter implements Runnable {

protected final String remoteHostName;
protected final Logger log = Logger.getLogger(getClass().getName());
private final String database;
private final InfluxDB influxdb;

public BaseSeleniumReporter(String remoteHostName, InfluxDB influxdb, String database) {
this.remoteHostName = remoteHostName;
this.influxdb = influxdb;
this.database = database;
}

@Override
public final void run() {
try {
report();
} catch (Exception e) {
log.warning(e.getMessage());
}
}

protected abstract void report();

protected void write(Point... points) {
for (Point p : points) {
influxdb.write(p);
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/xing/qa/selenium/grid/node/CommandReporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.xing.qa.selenium.grid.node;

import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.openqa.grid.internal.ExternalSessionKey;
import org.openqa.grid.internal.TestSession;

import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
* CommandReporter
*
* @author Jens Hausherr (jens.hausherr@xing.com)
*/
class CommandReporter extends BaseSeleniumReporter {

protected final TestSession session;
protected final ContentSnoopingRequest request;
protected final HttpServletResponse response;
protected final ReportType type;

public CommandReporter(String remoteHostName, InfluxDB influxdb, String database, TestSession session, ContentSnoopingRequest request, HttpServletResponse response, ReportType type) {
super(remoteHostName, influxdb, database);
this.type = type;
this.request = request;
this.session = session;
this.response = response;
}

protected void report() {
ExternalSessionKey esk = session.getExternalKey();
String sessionKey = null;
if (esk != null) {
sessionKey = esk.getKey();
}

Point s = Point.measurement(String.format("session.cmd.%s.measure", type))
.addField("time",System.currentTimeMillis())
.addField("host",remoteHostName)
.addField("ext_key",sessionKey)
.addField("int_key",session.getInternalKey())
.addField("forwarding",session.isForwardingRequest())
.addField("orphaned",session.isOrphaned())
.addField("inactivity",session.getInactivityTime())
.addField("cmd_method",request.getMethod())
.addField("cmd_action",request.getPathInfo())
.addField("cmd",request.getContent())
.build();
write(s);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.xing.qa.selenium.grid.node;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.io.InputStream;

/**
* ContentSnoopingRequest wraps the original request to allow us to peek into the payload of the request.
*
* @author Jens Hausherr (jens.hausherr@xing.com)
*/
class ContentSnoopingRequest extends HttpServletRequestWrapper {

private String content;
private String encoding;

public String getContent() {
return content;
}

/**
* Constructs a request object wrapping the given request.
*
* @param request
* @throws IllegalArgumentException if the request is null
*/
public ContentSnoopingRequest(HttpServletRequest request) {
super(request);

encoding = request.getCharacterEncoding();
if (encoding == null)
encoding = "ISO-8859-1";

try {
StringBuilder sb = new StringBuilder();
InputStream is = request.getInputStream();
byte[] buffer = new byte[1024];
int read = 0;

while ((read = is.read(buffer, 0, 1024)) != -1) {
sb.append(new String(buffer, 0, read, encoding));
}

this.content = sb.toString();

} catch (IOException e) {
e.printStackTrace();
this.content = "";
}
}

@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {

int idx = 0;
byte[] contents = content.getBytes(encoding);

@Override
public boolean isFinished() {
return idx < contents.length;
}

@Override
public boolean isReady() {
return !isFinished();
}

@Override
public int read() throws IOException {
if (idx < contents.length) {
return contents[idx++];
} else return -1;
}

@Override
public void setReadListener(ReadListener listener) {
throw new java.lang.IllegalStateException();
}
};
}

}
33 changes: 33 additions & 0 deletions src/main/java/com/xing/qa/selenium/grid/node/ErrorReporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.xing.qa.selenium.grid.node;

import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.openqa.grid.common.exception.RemoteException;
import org.openqa.grid.internal.TestSession;

import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
* ErrorReporter
*
* @author Jens Hausherr (jens.hausherr@xing.com)
*/
class ErrorReporter extends BaseSeleniumReporter {

private final RemoteException exception;

public ErrorReporter(String remoteHostName, InfluxDB influxdb, String database, RemoteException ex) {
super(remoteHostName, influxdb, database);
this.exception = ex;
}

@Override
protected void report() {
final Point exRep = Point.measurement("node.errors").addField("time", System.currentTimeMillis())
.addField("host", remoteHostName).addField("error", exception.getClass().getName())
.addField("message", exception.getMessage()).build();
write(exRep);
}

}
Loading