Skip to content

Commit a66a884

Browse files
author
Andrei Kamarouski
committed
feature: selenium 4
1 parent 69d539b commit a66a884

30 files changed

+1882
-55
lines changed

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.project
2+
target
3+
bin
4+
.classpath
5+
/test-output
6+
/.settings
7+
**/.settings
8+
/com
9+
/application.log
10+
/sql.log
11+
*.checkstyle
12+
.idea
13+
*.iml
14+
test-output
15+
*.log
16+
/reports
17+
/out
18+
dependency-reduced-pom.xml
19+
.DS_Store

Dockerfile

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,34 @@ ENV APPIUM_APPS_DIR=/opt/appium-storage
1212
ENV APPIUM_APP_WAITING_TIMEOUT=600
1313
ENV APPIUM_MAX_LOCK_FILE_LIFETIME=1800
1414
ENV APPIUM_APP_FETCH_RETRIES=0
15-
ENV APPIUM_CLI=
16-
1715
ENV APPIUM_APP_SIZE_DISABLE=false
1816

19-
ENV APPIUM_PLUGINS=
17+
################################################
18+
######### NODE CONFIGURATION VARIABLES #########
19+
# Hub hostname or IP address
20+
ENV SELENIUM_HOST localhost
21+
# Hub port
22+
ENV SELENIUM_PORT 4444
23+
# How often, in seconds, the Node will try to register itself for the first time to the Distributor.
24+
ENV REGISTER_CYCLE 300
25+
# How long, in seconds, will the Node try to register to the Distributor for the first time.
26+
# After this period is completed, the Node will not attempt to register again.
27+
ENV REGISTER_PERIOD 1000
28+
# How often, in seconds, will the Node send heartbeat events to the Distributor to inform it that the Node is up.
29+
ENV HEARTBEAT_PERIOD 5
30+
# Let X be the session-timeout in seconds.
31+
# The Node will automatically kill a session that has not had any activity in the last X seconds.
32+
# This will release the slot for other tests.
33+
ENV GRID_BROWSER_TIMEOUT 180
34+
#todo add description
35+
ENV PUBLISH_EVENTS_PORT 4442
36+
#todo add description
37+
ENV SUBSCRIBE_EVENTS_PORT 4443
38+
# Log level. Default logging level is INFO. Log levels are described here
39+
# https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html
40+
ENV NODE_LOG_LEVEL INFO
41+
ENV HTTP_LOGS false
42+
################################################
2043

2144
# Default appium 2.0 ueser:
2245
# uid=1300(androidusr) gid=1301(androidusr) groups=1301(androidusr)
@@ -82,6 +105,16 @@ COPY files/check-wda.sh /opt
82105
COPY files/zbr-config-gen.sh /opt
83106
COPY files/zbr-default-caps-gen.sh /opt
84107

108+
COPY target/mcloud-node-1.0.jar \
109+
/opt
110+
COPY target/mcloud-node.jar \
111+
/opt
112+
113+
COPY agent/target/mcloud-node-agent-1.0.jar \
114+
/opt
115+
COPY agent/target/mcloud-node-agent.jar \
116+
/opt
117+
85118
ENV ENTRYPOINT_DIR=/opt/entrypoint
86119
RUN mkdir -p ${ENTRYPOINT_DIR}
87120
COPY entrypoint.sh ${ENTRYPOINT_DIR}

Dockerfile-opencv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM public.ecr.aws/zebrunner/appium:2.0.9-beta13
1+
FROM public.ecr.aws/zebrunner/appium:3.0-beta2
22

33
ENV APPIUM_PLUGINS=images
44

agent/pom.xml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<groupId>com.zebrunner</groupId>
7+
<artifactId>mcloud-node-agent</artifactId>
8+
<version>1.0</version>
9+
<packaging>jar</packaging>
10+
<name>Zebrunner Device Farm (Selenium Grid Node Agent)</name>
11+
<properties>
12+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13+
<maven-shade-plugin.version>3.5.0</maven-shade-plugin.version>
14+
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
15+
</properties>
16+
<dependencies>
17+
<dependency>
18+
<!--we need it to reuse benefits of zebrunner testng agent for webdriver
19+
sesssion(s) declaration -->
20+
<groupId>net.bytebuddy</groupId>
21+
<artifactId>byte-buddy</artifactId>
22+
<version>1.14.5</version>
23+
</dependency>
24+
</dependencies>
25+
<build>
26+
<plugins>
27+
<plugin>
28+
<groupId>org.apache.maven.plugins</groupId>
29+
<artifactId>maven-shade-plugin</artifactId>
30+
<version>${maven-shade-plugin.version}</version>
31+
<executions>
32+
<execution>
33+
<phase>package</phase>
34+
<goals>
35+
<goal>shade</goal>
36+
</goals>
37+
<configuration>
38+
<minimizeJar>false</minimizeJar>
39+
<transformers>
40+
<!-- Don't do this: Avoid adding anything that makes shade create or modify a manifest file.
41+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
42+
<mainClass>com.mypackage.MyMainClass</mainClass>
43+
</transformer>
44+
-->
45+
<!-- Add a transformer to exclude any other manifest files (possibly from dependencies). -->
46+
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
47+
<resource>MANIFEST.MF</resource>
48+
</transformer>
49+
50+
<!-- Add a transformer to include your custom manifest file. -->
51+
<transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
52+
<resource>META-INF/MANIFEST.MF</resource>
53+
<file>src/main/resources/META-INF/MANIFEST.MF</file>
54+
</transformer>
55+
</transformers>
56+
<filters>
57+
<filter>
58+
<artifact>*:*</artifact>
59+
<excludes>
60+
<exclude>META-INF/*.SF</exclude>
61+
<exclude>META-INF/*.DSA</exclude>
62+
<exclude>META-INF/*.RSA</exclude>
63+
</excludes>
64+
</filter>
65+
</filters>
66+
<finalName>mcloud-node-agent</finalName>
67+
</configuration>
68+
</execution>
69+
</executions>
70+
</plugin>
71+
<plugin>
72+
<groupId>org.apache.maven.plugins</groupId>
73+
<artifactId>maven-compiler-plugin</artifactId>
74+
<version>${maven-compiler-plugin.version}</version>
75+
<configuration>
76+
<source>11</source>
77+
<target>11</target>
78+
</configuration>
79+
</plugin>
80+
</plugins>
81+
</build>
82+
</project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.zebrunner.mcloud.grid.agent;
2+
3+
import net.bytebuddy.agent.builder.AgentBuilder;
4+
import net.bytebuddy.description.method.MethodDescription;
5+
import net.bytebuddy.description.type.TypeDescription;
6+
import net.bytebuddy.dynamic.DynamicType;
7+
import net.bytebuddy.matcher.ElementMatcher;
8+
import net.bytebuddy.matcher.NameMatcher;
9+
import net.bytebuddy.pool.TypePool;
10+
11+
import java.lang.instrument.Instrumentation;
12+
import java.util.logging.Logger;
13+
14+
import static net.bytebuddy.implementation.MethodDelegation.to;
15+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
16+
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
17+
import static net.bytebuddy.matcher.ElementMatchers.named;
18+
import static net.bytebuddy.matcher.ElementMatchers.not;
19+
20+
public class NodeAgent {
21+
private static final Logger LOGGER = Logger.getLogger(NodeAgent.class.getName());
22+
private static final String RELAY_SESSION_FACTORY_CLASS = "org.openqa.selenium.grid.node.relay.RelaySessionFactory";
23+
private static final String TEST_METHOD_NAME = "test";
24+
25+
public static void premain(String args, Instrumentation instrumentation) {
26+
try {
27+
new AgentBuilder.Default()
28+
.with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager())
29+
.type(named(RELAY_SESSION_FACTORY_CLASS))
30+
.transform((builder, type, classloader, module, protectionDomain) -> addTestMethodInterceptor(builder))
31+
.installOn(instrumentation);
32+
} catch (Exception e) {
33+
LOGGER.warning(() -> "Could not init instrumentation.");
34+
}
35+
}
36+
37+
private static DynamicType.Builder<?> addTestMethodInterceptor(DynamicType.Builder<?> builder) {
38+
return builder.method(isTestMethod())
39+
.intercept(to(testMethodInterceptor()));
40+
}
41+
42+
public static ElementMatcher<? super MethodDescription> isTestMethod() {
43+
return isPublic()
44+
.and(not(isStatic()))
45+
.and(new NameMatcher<>(TEST_METHOD_NAME::equals));
46+
}
47+
48+
private static TypeDescription testMethodInterceptor() {
49+
return TypePool.Default.ofSystemLoader()
50+
.describe(RelaySessionFactoryInterceptor.class.getName())
51+
.resolve();
52+
}
53+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.zebrunner.mcloud.grid.agent;
2+
3+
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
4+
import net.bytebuddy.implementation.bind.annotation.SuperCall;
5+
import net.bytebuddy.implementation.bind.annotation.This;
6+
7+
import java.util.concurrent.Callable;
8+
9+
public class RelaySessionFactoryInterceptor {
10+
11+
@RuntimeType
12+
public static Object onTestMethodInvocation(@This final Object factory,
13+
@SuperCall final Callable<Object> proxy) throws Exception {
14+
return true;
15+
}
16+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Manifest-Version: 1.0
2+
Premain-Class: com.zebrunner.mcloud.grid.agent.NodeAgent
3+
Can-Redefine-Classes: true
4+
Can-Retransform-Classes: true

entrypoint.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22

3-
NODE_CONFIG_JSON="/root/nodeconfig.json"
3+
NODE_CONFIG_JSON="/root/nodeconfig.toml"
44
DEFAULT_CAPABILITIES_JSON="/root/defaultcapabilities.json"
55

66
# show list of plugins including installed ones
@@ -12,7 +12,7 @@ if [[ -n $APPIUM_PLUGINS ]]; then
1212
echo "plugins_cli: $plugins_cli"
1313
fi
1414

15-
CMD="xvfb-run appium --log-no-colors --log-timestamp -pa /wd/hub --port $APPIUM_PORT --log $TASK_LOG --log-level $LOG_LEVEL $APPIUM_CLI $plugins_cli"
15+
CMD="xvfb-run appium --log-no-colors --log-timestamp -pa /wd/hub --port 4723 --log $TASK_LOG --log-level $LOG_LEVEL $APPIUM_CLI $plugins_cli"
1616
#--use-plugins=relaxed-caps
1717

1818
share() {
@@ -341,7 +341,7 @@ if [ "$CONNECT_TO_GRID" = true ]; then
341341
else
342342
/root/generate_config.sh $NODE_CONFIG_JSON
343343
fi
344-
CMD+=" --nodeconfig $NODE_CONFIG_JSON"
344+
# CMD+=" --nodeconfig $NODE_CONFIG_JSON"
345345
fi
346346

347347
if [ "$DEFAULT_CAPABILITIES" = true ]; then
@@ -365,8 +365,11 @@ rm -rf /tmp/.X99-lock
365365

366366
touch ${TASK_LOG}
367367
echo $CMD
368-
$CMD &
369368

369+
$CMD &
370+
java ${JAVA_OPTS} -cp /opt/mcloud-node-1.0.jar:/opt/mcloud-node.jar -javaagent:/opt/mcloud-node-agent.jar org.openqa.selenium.grid.Bootstrap node \
371+
--port 7777 \
372+
--config $NODE_CONFIG_JSON &
370373
trap 'finish' SIGTERM
371374

372375
# start in background video artifacts capturing

files/android.sh

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ else
3030
export DEVICETYPE='Phone'
3131
fi
3232

33-
if [[ ${PLATFORM_VERSION} == 4* ]] || [[ ${PLATFORM_VERSION} == 5* ]] || [[ ${PLATFORM_VERSION} == 6* ]]
34-
then
35-
export AUTOMATION_NAME='Appium'
36-
else
37-
export AUTOMATION_NAME='uiautomator2'
38-
fi
33+
export AUTOMATION_NAME='uiautomator2'
3934

4035
# there is no sense to clean something for scalable one-time redroid container
4136
if [ "$ANDROID_DEVICE" != "device:5555" ]; then

files/zbr-config-gen.sh

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,59 @@
11
#!/bin/bash
22

3-
#IMPORTANT!!! Don't do any echo otherwise you corrupt generated nodeconfig.json
3+
#IMPORTANT!!! Don't do any echo otherwise you corrupt generated nodeconfig.toml
44
# convert to lower case using Linux/Mac compatible syntax (bash v3.2)
55
PLATFORM_NAME=`echo "$PLATFORM_NAME" | tr '[:upper:]' '[:lower:]'`
66
cat << EndOfMessage
7-
{
8-
"capabilities":
9-
[
10-
{
11-
"maxInstances": 1,
12-
"deviceName": "${DEVICE_NAME}",
13-
"deviceType": "${DEVICETYPE}",
14-
"platformName":"${PLATFORM_NAME}",
15-
"platformVersion":"${PLATFORM_VERSION}",
16-
"udid": "${DEVICE_UDID}",
17-
"adb_port": ${ADB_PORT},
18-
"proxy_port": ${PROXY_PORT},
19-
"automationName": "${AUTOMATION_NAME}"
20-
}
21-
],
22-
"configuration":
23-
{
24-
"proxy": "com.zebrunner.mcloud.grid.MobileRemoteProxy",
25-
"url":"http://${STF_PROVIDER_HOST}:${APPIUM_PORT}/wd/hub",
26-
"host": "${STF_PROVIDER_HOST}",
27-
"port": ${APPIUM_PORT},
28-
"hubHost": "${SELENIUM_HOST}",
29-
"hubPort": ${SELENIUM_PORT},
30-
"maxSession": 1,
31-
"register": true,
32-
"registerCycle": 300000,
33-
"cleanUpCycle": 5000,
34-
"timeout": 180,
35-
"browserTimeout": 0,
36-
"nodeStatusCheckTimeout": 5000,
37-
"nodePolling": 5000,
38-
"role": "node",
39-
"unregisterIfStillDownAfter": ${UNREGISTER_IF_STILL_DOWN_AFTER},
40-
"downPollingLimit": 2,
41-
"debug": false,
42-
"servlets" : [],
43-
"withoutServlets": [],
44-
"custom": {}
45-
}
46-
}
7+
[node]
8+
# Autodetect which drivers are available on the current system, and add them to the Node.
9+
detect-drivers = false
10+
11+
# Maximum number of concurrent sessions. Default value is the number of available processors.
12+
max-sessions = 1
13+
14+
# Full classname of non-default Node implementation. This is used to manage a session’s lifecycle.
15+
implementation = "com.zebrunner.mcloud.grid.MobileRemoteProxy"
16+
17+
# The address of the Hub in a Hub-and-Node configuration.
18+
hub = "http://${SELENIUM_HOST}:${SELENIUM_PORT}"
19+
20+
# How often, in seconds, the Node will try to register itself for the first time to the Distributor.
21+
register-cycle = $REGISTER_CYCLE
22+
23+
# How long, in seconds, will the Node try to register to the Distributor for the first time.
24+
# After this period is completed, the Node will not attempt to register again.
25+
register-period = $REGISTER_PERIOD
26+
27+
# How often, in seconds, will the Node send heartbeat events to the Distributor to inform it that the Node is up.
28+
heartbeat-period = $HEARTBEAT_PERIOD
29+
30+
# Let X be the session-timeout in seconds.
31+
# The Node will automatically kill a session that has not had any activity in the last X seconds.
32+
# This will release the slot for other tests.
33+
session-timeout = $GRID_BROWSER_TIMEOUT
34+
35+
[relay]
36+
# URL for connecting to the service that supports WebDriver commands like an Appium server or a cloud service.
37+
url = "http://localhost:4723/wd/hub"
38+
39+
# Optional, endpoint to query the WebDriver service status, an HTTP 200 response is expected
40+
status-endpoint = "/status"
41+
42+
# Stereotypes supported by the service. The initial number is "max-sessions", and will allocate
43+
# that many test slots to that particular configuration
44+
configs = [
45+
"1", "{\"platformName\": \"${PLATFORM_NAME}\", \"appium:platformVersion\": \"${PLATFORM_VERSION}\", \"appium:deviceName\": \"${DEVICE_NAME}\", \"appium:automationName\": \"${AUTOMATION_NAME}\", \"zebrunner:deviceType\": \"${DEVICETYPE}\", \"appium:udid\": \"${DEVICE_UDID}\", \"zebrunner:adb_port\": \"${ADB_PORT}\", \"zebrunner:proxy_port\": \"${PROXY_PORT}\" }"
46+
]
47+
48+
[logging]
49+
# Log level. Default logging level is INFO. Log levels are described here
50+
# https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html
51+
log-level = "${NODE_LOG_LEVEL}"
52+
53+
# Enable http logging. Tracing should be enabled to log http logs.
54+
http-logs = "${HTTP_LOGS}"
55+
56+
[events]
57+
publish-events = "tcp://${SELENIUM_HOST}:${PUBLISH_EVENTS_PORT}"
58+
subscribe-events = "tcp://${SELENIUM_HOST}:${SUBSCRIBE_EVENTS_PORT}"
4759
EndOfMessage

0 commit comments

Comments
 (0)