diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..183416b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+.project
+target
+bin
+.classpath
+/test-output
+/.settings
+**/.settings
+/com
+/application.log
+/sql.log
+*.checkstyle
+.idea
+*.iml
+test-output
+*.log
+/reports
+/out
+dependency-reduced-pom.xml
+.DS_Store
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index c971fa0..cf0c928 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -64,6 +64,8 @@ ENV DEVICE_BUS=/dev/bus/usb/003/011
# Usbmuxd settings "host:port"
ENV USBMUXD_SOCKET_ADDRESS=
+ENV GRID_BROWSER_TIMEOUT 180
+
#Setup libimobile device, usbmuxd and some tools
RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get -y install iputils-ping nano jq telnet netcat curl ffmpeg libimobiledevice-utils libimobiledevice6 usbmuxd socat
@@ -82,6 +84,16 @@ COPY files/check-wda.sh /opt
COPY files/zbr-config-gen.sh /opt
COPY files/zbr-default-caps-gen.sh /opt
+COPY target/mcloud-node-1.0.jar \
+ /opt
+COPY target/mcloud-node.jar \
+ /opt
+
+COPY agent/target/mcloud-node-agent-1.0.jar \
+ /opt
+COPY agent/target/mcloud-node-agent.jar \
+ /opt
+
ENV ENTRYPOINT_DIR=/opt/entrypoint
RUN mkdir -p ${ENTRYPOINT_DIR}
COPY entrypoint.sh ${ENTRYPOINT_DIR}
diff --git a/agent/pom.xml b/agent/pom.xml
new file mode 100644
index 0000000..5969ae8
--- /dev/null
+++ b/agent/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+ com.zebrunner
+ mcloud-node-agent
+ 1.0
+ jar
+ Zebrunner Device Farm (Selenium Grid Node Agent)
+
+ UTF-8
+ 3.5.0
+ 3.11.0
+
+
+
+
+ net.bytebuddy
+ byte-buddy
+ 1.14.5
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+ package
+
+ shade
+
+
+ false
+
+
+
+
+ MANIFEST.MF
+
+
+
+
+ META-INF/MANIFEST.MF
+ src/main/resources/META-INF/MANIFEST.MF
+
+
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+ mcloud-node-agent
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ 11
+
+
+
+
+
diff --git a/agent/src/main/java/com/zebrunner/mcloud/grid/agent/NodeAgent.java b/agent/src/main/java/com/zebrunner/mcloud/grid/agent/NodeAgent.java
new file mode 100644
index 0000000..86885bd
--- /dev/null
+++ b/agent/src/main/java/com/zebrunner/mcloud/grid/agent/NodeAgent.java
@@ -0,0 +1,53 @@
+package com.zebrunner.mcloud.grid.agent;
+
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType;
+import net.bytebuddy.matcher.ElementMatcher;
+import net.bytebuddy.matcher.NameMatcher;
+import net.bytebuddy.pool.TypePool;
+
+import java.lang.instrument.Instrumentation;
+import java.util.logging.Logger;
+
+import static net.bytebuddy.implementation.MethodDelegation.to;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.isStatic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+
+public class NodeAgent {
+ private static final Logger LOGGER = Logger.getLogger(NodeAgent.class.getName());
+ private static final String RELAY_SESSION_FACTORY_CLASS = "org.openqa.selenium.grid.node.relay.RelaySessionFactory";
+ private static final String TEST_METHOD_NAME = "test";
+
+ public static void premain(String args, Instrumentation instrumentation) {
+ try {
+ new AgentBuilder.Default()
+ .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
+ .type(named(RELAY_SESSION_FACTORY_CLASS))
+ .transform((builder, type, classloader, module, protectionDomain) -> addTestMethodInterceptor(builder))
+ .installOn(instrumentation);
+ } catch (Exception e) {
+ LOGGER.warning(() -> "Could not init instrumentation.");
+ }
+ }
+
+ private static DynamicType.Builder> addTestMethodInterceptor(DynamicType.Builder> builder) {
+ return builder.method(isTestMethod())
+ .intercept(to(testMethodInterceptor()));
+ }
+
+ public static ElementMatcher super MethodDescription> isTestMethod() {
+ return isPublic()
+ .and(not(isStatic()))
+ .and(new NameMatcher<>(TEST_METHOD_NAME::equals));
+ }
+
+ private static TypeDescription testMethodInterceptor() {
+ return TypePool.Default.ofSystemLoader()
+ .describe(RelaySessionFactoryInterceptor.class.getName())
+ .resolve();
+ }
+}
diff --git a/agent/src/main/java/com/zebrunner/mcloud/grid/agent/RelaySessionFactoryInterceptor.java b/agent/src/main/java/com/zebrunner/mcloud/grid/agent/RelaySessionFactoryInterceptor.java
new file mode 100644
index 0000000..d91d809
--- /dev/null
+++ b/agent/src/main/java/com/zebrunner/mcloud/grid/agent/RelaySessionFactoryInterceptor.java
@@ -0,0 +1,16 @@
+package com.zebrunner.mcloud.grid.agent;
+
+import net.bytebuddy.implementation.bind.annotation.RuntimeType;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+import net.bytebuddy.implementation.bind.annotation.This;
+
+import java.util.concurrent.Callable;
+
+public class RelaySessionFactoryInterceptor {
+
+ @RuntimeType
+ public static Object onTestMethodInvocation(@This final Object factory,
+ @SuperCall final Callable