Skip to content

Commit 8848360

Browse files
authored
SE_NODE_STEREOTYPE_EXTRA to append custom capabilities to default Node stereotype (#2624)
* SE_NODE_STEREOTYPE_EXTRA to append custom capabilities to default Node stereotype * Add test --------- Signed-off-by: Viet Nguyen Duc <[email protected]>
1 parent a396474 commit 8848360

11 files changed

+231
-7
lines changed

ENV_VARIABLES.md

+22-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
| SE_HTTPS_PRIVATE_KEY | /opt/selenium/secrets/tls.key | | |
5454
| SE_ENABLE_TRACING | true | | |
5555
| SE_OTEL_EXPORTER_ENDPOINT | | | |
56-
| SE_OTEL_SERVICE_NAME | selenium-standalone-docker | | |
56+
| SE_OTEL_SERVICE_NAME | selenium-router | | |
5757
| SE_OTEL_JVM_ARGS | | | |
5858
| SE_OTEL_TRACES_EXPORTER | otlp | | |
5959
| SE_OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED | true | | |
@@ -121,3 +121,24 @@
121121
| SE_SUPERVISORD_START_RETRIES | 5 | | |
122122
| SE_RECORD_AUDIO | false | Flag to enable recording the audio source (default is Pulse Audio input) | |
123123
| SE_AUDIO_SOURCE | -f pulse -ac 2 -i default | FFmpeg arguments to record the audio source | |
124+
| SE_BROWSER_BINARY_LOCATION | | | |
125+
| SE_NODE_BROWSER_NAME | | | |
126+
| SE_NODE_CONTAINER_NAME | | | |
127+
| SE_NODE_HOST | | | |
128+
| SE_NODE_MAX_CONCURRENCY | | When node is handled both browser and relay, SE_NODE_MAX_CONCURRENCY is used to configure max concurrency based on sum of them | |
129+
| SE_NODE_RELAY_BROWSER_NAME | | | |
130+
| SE_NODE_RELAY_MAX_SESSIONS | | | |
131+
| SE_NODE_RELAY_PLATFORM_NAME | | | |
132+
| SE_NODE_RELAY_PLATFORM_VERSION | | | |
133+
| SE_NODE_RELAY_PROTOCOL_VERSION | | | |
134+
| SE_NODE_RELAY_STATUS_ENDPOINT | | | |
135+
| SE_NODE_RELAY_URL | | | |
136+
| SE_NODE_STEREOTYPE | | Capabilities in JSON string to overwrite the default Node stereotype | |
137+
| SE_NODE_STEREOTYPE_EXTRA | | Extra capabilities in JSON string that wants to merge to the default Node stereotype | |
138+
| SE_SESSIONS_MAP_EXTERNAL_HOSTNAME | | | |
139+
| SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION | | | |
140+
| SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD | | | |
141+
| SE_SESSIONS_MAP_EXTERNAL_JDBC_URL | | | |
142+
| SE_SESSIONS_MAP_EXTERNAL_JDBC_USER | | | |
143+
| SE_SESSIONS_MAP_EXTERNAL_PORT | | | |
144+
| SE_SESSIONS_MAP_EXTERNAL_SCHEME | | | |

NodeBase/Dockerfile

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ COPY --chown="${SEL_UID}:${SEL_GID}" start-selenium-node.sh \
152152
start-xvfb.sh \
153153
start-vnc.sh \
154154
start-novnc.sh \
155-
generate_config generate_relay_config /opt/bin/
155+
generate_config generate_relay_config json_merge.py /opt/bin/
156+
RUN chmod +x /opt/bin/*.sh /opt/bin/*.py /opt/bin/generate_*
156157

157158
# Selenium Grid logo as wallpaper for Fluxbox
158159
COPY selenium_grid_logo.png /usr/share/images/fluxbox/ubuntu-light.png

NodeBase/generate_config

+11-1
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,23 @@ fi
6161
if [ -f /opt/selenium/browser_binary_location ] && [ -z "${SE_BROWSER_BINARY_LOCATION}" ]; then
6262
SE_BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location)
6363
fi
64+
SE_NODE_CONTAINER_NAME="${SE_NODE_CONTAINER_NAME:-$(hostname)}"
6465

6566
# 'browserName' is mandatory for default stereotype
6667
if [[ -z "${SE_NODE_STEREOTYPE}" ]] && [[ -n "${SE_NODE_BROWSER_NAME}" ]]; then
67-
SE_NODE_STEREOTYPE="{\"browserName\": \"${SE_NODE_BROWSER_NAME}\", \"browserVersion\": \"${SE_NODE_BROWSER_VERSION}\", \"platformName\": \"${SE_NODE_PLATFORM_NAME}\", ${SE_BROWSER_BINARY_LOCATION}, \"se:containerName\": \"${SE_NODE_CONTAINER_NAME}\"}"
68+
SE_NODE_STEREOTYPE="{\"browserName\": \"${SE_NODE_BROWSER_NAME}\", \"browserVersion\": \"${SE_NODE_BROWSER_VERSION}\", \"platformName\": \"${SE_NODE_PLATFORM_NAME}\", ${SE_BROWSER_BINARY_LOCATION}, \"se:containerName\": \"${SE_NODE_CONTAINER_NAME}\", \"container:hostname\": \"$(hostname)\"}"
6869
else
6970
SE_NODE_STEREOTYPE="${SE_NODE_STEREOTYPE}"
7071
fi
72+
if [[ -n "${SE_NODE_STEREOTYPE_EXTRA}" ]]; then
73+
echo "Merging SE_NODE_STEREOTYPE_EXTRA=${SE_NODE_STEREOTYPE_EXTRA} to main stereotype"
74+
SE_NODE_STEREOTYPE="$(python3 /opt/bin/json_merge.py "${SE_NODE_STEREOTYPE}" "${SE_NODE_STEREOTYPE_EXTRA}")"
75+
if [[ $? -ne 0 ]]; then
76+
echo "Failed to merge SE_NODE_STEREOTYPE_EXTRA. Please check the format of the JSON string. Keep using main stereotype."
77+
else
78+
echo "Merged stereotype: ${SE_NODE_STEREOTYPE}"
79+
fi
80+
fi
7181

7282
# 'stereotype' setting is mandatory
7383
if [[ -n "${SE_NODE_STEREOTYPE}" ]]; then

NodeBase/json_merge.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import json
2+
import sys
3+
4+
json_str1 = sys.argv[1]
5+
json_str2 = sys.argv[2]
6+
7+
try:
8+
# Parse JSON strings into dictionaries
9+
dict1 = json.loads(json_str1)
10+
dict2 = json.loads(json_str2)
11+
# Merge dictionaries
12+
merged_dict = {**dict1, **dict2}
13+
# Convert merged dictionary back to JSON string
14+
merged_json_str = json.dumps(merged_dict, separators=(',', ':'), ensure_ascii=True)
15+
# Print the merged JSON string
16+
print(merged_json_str)
17+
except:
18+
# Print the first JSON string if an error occurs
19+
print(json_str1)
20+
sys.exit(1)

README.md

+52-1
Original file line numberDiff line numberDiff line change
@@ -1112,10 +1112,61 @@ Here is an example with the default values of these environment variables:
11121112
$ docker run -d \
11131113
-e SE_EVENT_BUS_HOST=<event_bus_ip|event_bus_name> \
11141114
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
1115-
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 -e SE_NODE_STEREOTYPE="{\"browserName\":\"${SE_NODE_BROWSER_NAME}\",\"browserVersion\":\"${SE_NODE_BROWSER_VERSION}\",\"platformName\": \"Linux\"}" \
1115+
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
1116+
-e SE_NODE_STEREOTYPE="{\"browserName\":\"${SE_NODE_BROWSER_NAME}\", \"browserVersion\":\"${SE_NODE_BROWSER_VERSION}\", \"platformName\":\"${SE_NODE_PLATFORM_NAME}\"}" \
11161117
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
11171118
```
11181119
1120+
In another case, if you want to retain the default Node stereotype and append additional capabilities, you can use the `SE_NODE_STEREOTYPE_EXTRA` environment variable to set your capabilities. Those will be merged to the default stereotype. For example:
1121+
```bash
1122+
$ docker run -d \
1123+
-e SE_EVENT_BUS_HOST=<event_bus_ip|event_bus_name> \
1124+
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
1125+
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
1126+
-e SE_NODE_STEREOTYPE_EXTRA="{\"myApp:version\":\"beta\", \"myApp:publish:\":\"public\"}" \
1127+
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
1128+
```
1129+
1130+
This help setting custom capabilities for matching specific Nodes. For example, you added your custom capabilities when starting the Node, and you want assign a test to run on that Node which matches your capabilities. For example in test code:
1131+
1132+
```python
1133+
options = ChromeOptions()
1134+
options.set_capability('myApp:version', 'beta')
1135+
options.set_capability('myApp:publish', 'public')
1136+
driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL)
1137+
```
1138+
1139+
Noted: Your custom capabilities with key values should be in W3C capabilities convention, extension capabilities key must contain a ":" (colon) character, denoting an implementation specific namespace.
1140+
1141+
Noted: Ensure that Node config `detect-drivers = false` in `config.toml` (or `--detect-drivers false` in CLI option) to make feature setting custom capabilities for matching specific Nodes get working.
1142+
1143+
In addition, default Node stereotype includes capability `se:containerName` which can visible in node capabilities, or session capabilities to identify the container name where the node/session is running. **The prefixed `se:containerName` is not included in slot matcher**. By default, value is getting from `hostname` command in container, this value is equivalent to the `container_id` that you saw via `docker ps` command. If you want to override this value, you can set the environment variable `SE_NODE_CONTAINER_NAME` to your desired value. For example, when deploy to Kubernetes cluster, you can assign Pod name to env var `SE_NODE_CONTAINER_NAME` to track a node is running in which Pod.
1144+
1145+
```yaml
1146+
env:
1147+
- name: SE_NODE_CONTAINER_NAME
1148+
valueFrom:
1149+
fieldRef:
1150+
fieldPath: metadata.name
1151+
```
1152+
1153+
In an advanced case, where you control to spawn up a Node container, let it register to Hub, and then trigger a test to be assigned exactly to run on that Node. By default, the value of command `$(hostname)` is added to capability name `container:hostname` in Node stereotype. Combine with above feature setting custom capabilities for matching specific Nodes. You can use the `hostname` of the Node container just spawned up and set it as a custom capability. For example, in Python binding:
1154+
1155+
```bash
1156+
$ docker run -d --name my-node-1 -e SE_EVENT_BUS_HOST=localhost -e SE_EVENT_BUS_PUBLISH_PORT=4442 -e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
1157+
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
1158+
$ docker exec -i my-node-1 hostname
1159+
a6971f95bbab
1160+
```
1161+
1162+
```python
1163+
options = ChromeOptions()
1164+
options.set_capability('container:hostname', 'a6971f95bbab')
1165+
driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL)
1166+
```
1167+
1168+
_Noted: Those above changes require new image tag where the changeset is included & released._
1169+
11191170
### Node configuration relay commands
11201171
11211172
Relaying commands to a service endpoint that supports WebDriver.

Standalone/generate_config

+11-1
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,22 @@ fi
4141
if [ -f /opt/selenium/browser_binary_location ] && [ -z "${SE_BROWSER_BINARY_LOCATION}" ]; then
4242
SE_BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location)
4343
fi
44+
SE_NODE_CONTAINER_NAME="${SE_NODE_CONTAINER_NAME:-$(hostname)}"
4445

4546
if [[ -z "$SE_NODE_STEREOTYPE" ]]; then
46-
SE_NODE_STEREOTYPE="{\"browserName\": \"${SE_NODE_BROWSER_NAME}\", \"browserVersion\": \"${SE_NODE_BROWSER_VERSION}\", \"platformName\": \"${SE_NODE_PLATFORM_NAME}\", ${SE_BROWSER_BINARY_LOCATION}, \"se:containerName\": \"${SE_NODE_CONTAINER_NAME}\"}"
47+
SE_NODE_STEREOTYPE="{\"browserName\": \"${SE_NODE_BROWSER_NAME}\", \"browserVersion\": \"${SE_NODE_BROWSER_VERSION}\", \"platformName\": \"${SE_NODE_PLATFORM_NAME}\", ${SE_BROWSER_BINARY_LOCATION}, \"se:containerName\": \"${SE_NODE_CONTAINER_NAME}\", \"container:hostname\": \"$(hostname)\"}"
4748
else
4849
SE_NODE_STEREOTYPE="$SE_NODE_STEREOTYPE"
4950
fi
51+
if [[ -n "${SE_NODE_STEREOTYPE_EXTRA}" ]]; then
52+
echo "Merging SE_NODE_STEREOTYPE_EXTRA=${SE_NODE_STEREOTYPE_EXTRA} to main stereotype"
53+
SE_NODE_STEREOTYPE="$(python3 /opt/bin/json_merge.py "${SE_NODE_STEREOTYPE}" "${SE_NODE_STEREOTYPE_EXTRA}")"
54+
if [[ $? -ne 0 ]]; then
55+
echo "Failed to merge SE_NODE_STEREOTYPE_EXTRA. Please check the format of the JSON string. Keep using main stereotype."
56+
else
57+
echo "Merged stereotype: ${SE_NODE_STEREOTYPE}"
58+
fi
59+
fi
5060

5161
echo "[[node.driver-configuration]]" >>"$FILENAME"
5262
echo "display-name = \"${SE_NODE_BROWSER_NAME}\"" >>"$FILENAME"

scripts/generate_list_env_vars/description.yaml

+65
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,68 @@
366366
- name: SE_AUDIO_SOURCE
367367
description: FFmpeg arguments to record the audio source
368368
cli: ''
369+
- name: SE_BROWSER_BINARY_LOCATION
370+
description: ''
371+
cli: ''
372+
- name: SE_NODE_BROWSER_NAME
373+
description: ''
374+
cli: ''
375+
- name: SE_NODE_CONTAINER_NAME
376+
description: ''
377+
cli: ''
378+
- name: SE_NODE_HOST
379+
description: ''
380+
cli: ''
381+
- name: SE_NODE_MAX_CONCURRENCY
382+
description: When node is handled both browser and relay, SE_NODE_MAX_CONCURRENCY
383+
is used to configure max concurrency based on sum of them
384+
cli: ''
385+
- name: SE_NODE_RELAY_BROWSER_NAME
386+
description: ''
387+
cli: ''
388+
- name: SE_NODE_RELAY_MAX_SESSIONS
389+
description: ''
390+
cli: ''
391+
- name: SE_NODE_RELAY_PLATFORM_NAME
392+
description: ''
393+
cli: ''
394+
- name: SE_NODE_RELAY_PLATFORM_VERSION
395+
description: ''
396+
cli: ''
397+
- name: SE_NODE_RELAY_PROTOCOL_VERSION
398+
description: ''
399+
cli: ''
400+
- name: SE_NODE_RELAY_STATUS_ENDPOINT
401+
description: ''
402+
cli: ''
403+
- name: SE_NODE_RELAY_URL
404+
description: ''
405+
cli: ''
406+
- name: SE_NODE_STEREOTYPE
407+
description: Capabilities in JSON string to overwrite the default Node stereotype
408+
cli: ''
409+
- name: SE_NODE_STEREOTYPE_EXTRA
410+
description: Extra capabilities in JSON string that wants to merge to the default
411+
Node stereotype
412+
cli: ''
413+
- name: SE_SESSIONS_MAP_EXTERNAL_HOSTNAME
414+
description: ''
415+
cli: ''
416+
- name: SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION
417+
description: ''
418+
cli: ''
419+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD
420+
description: ''
421+
cli: ''
422+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_URL
423+
description: ''
424+
cli: ''
425+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_USER
426+
description: ''
427+
cli: ''
428+
- name: SE_SESSIONS_MAP_EXTERNAL_PORT
429+
description: ''
430+
cli: ''
431+
- name: SE_SESSIONS_MAP_EXTERNAL_SCHEME
432+
description: ''
433+
cli: ''

scripts/generate_list_env_vars/extract_env.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def extract_variables_from_shell_scripts(directory_path):
88
for root, _, files in os.walk(directory_path):
99
files.sort()
1010
for file in files:
11-
if file.endswith(".sh"):
11+
if file.endswith(".sh") or file.startswith("generate_"):
1212
file_path = os.path.join(root, file)
1313
try:
1414
with open(file_path, 'r') as f:

scripts/generate_list_env_vars/value.yaml

+43-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
default: -f pulse -ac 2 -i default
33
- name: SE_BIND_HOST
44
default: 'false'
5+
- name: SE_BROWSER_BINARY_LOCATION
6+
default: ''
57
- name: SE_BROWSER_LEFTOVERS_INTERVAL_SECS
68
default: '3600'
79
- name: SE_BROWSER_LEFTOVERS_PROCESSES_SECS
@@ -72,8 +74,12 @@
7274
default: '%Y-%m-%d %H:%M:%S,%3N'
7375
- name: SE_NEW_SESSION_THREAD_POOL_SIZE
7476
default: ''
77+
- name: SE_NODE_BROWSER_NAME
78+
default: ''
7579
- name: SE_NODE_BROWSER_VERSION
7680
default: stable
81+
- name: SE_NODE_CONTAINER_NAME
82+
default: ''
7783
- name: SE_NODE_DOCKER_CONFIG_FILENAME
7884
default: ''
7985
- name: SE_NODE_ENABLE_CDP
@@ -88,6 +94,10 @@
8894
default: ''
8995
- name: SE_NODE_HEARTBEAT_PERIOD
9096
default: '30'
97+
- name: SE_NODE_HOST
98+
default: ''
99+
- name: SE_NODE_MAX_CONCURRENCY
100+
default: ''
91101
- name: SE_NODE_MAX_SESSIONS
92102
default: '1'
93103
- name: SE_NODE_OVERRIDE_MAX_SESSIONS
@@ -102,8 +112,26 @@
102112
default: ''
103113
- name: SE_NODE_REGISTER_PERIOD
104114
default: ''
115+
- name: SE_NODE_RELAY_BROWSER_NAME
116+
default: ''
117+
- name: SE_NODE_RELAY_MAX_SESSIONS
118+
default: ''
119+
- name: SE_NODE_RELAY_PLATFORM_NAME
120+
default: ''
121+
- name: SE_NODE_RELAY_PLATFORM_VERSION
122+
default: ''
123+
- name: SE_NODE_RELAY_PROTOCOL_VERSION
124+
default: ''
125+
- name: SE_NODE_RELAY_STATUS_ENDPOINT
126+
default: ''
127+
- name: SE_NODE_RELAY_URL
128+
default: ''
105129
- name: SE_NODE_SESSION_TIMEOUT
106130
default: '300'
131+
- name: SE_NODE_STEREOTYPE
132+
default: ''
133+
- name: SE_NODE_STEREOTYPE_EXTRA
134+
default: ''
107135
- name: SE_NO_VNC_PORT
108136
default: '7900'
109137
- name: SE_OFFLINE
@@ -117,7 +145,7 @@
117145
- name: SE_OTEL_JVM_ARGS
118146
default: ''
119147
- name: SE_OTEL_SERVICE_NAME
120-
default: selenium-standalone-docker
148+
default: selenium-router
121149
- name: SE_OTEL_TRACES_EXPORTER
122150
default: otlp
123151
- name: SE_PRESET
@@ -158,6 +186,20 @@
158186
default: ''
159187
- name: SE_SESSIONS_MAP_EXTERNAL_DATASTORE
160188
default: 'false'
189+
- name: SE_SESSIONS_MAP_EXTERNAL_HOSTNAME
190+
default: ''
191+
- name: SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION
192+
default: ''
193+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD
194+
default: ''
195+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_URL
196+
default: ''
197+
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_USER
198+
default: ''
199+
- name: SE_SESSIONS_MAP_EXTERNAL_PORT
200+
default: ''
201+
- name: SE_SESSIONS_MAP_EXTERNAL_SCHEME
202+
default: ''
161203
- name: SE_SESSIONS_MAP_HOST
162204
default: ''
163205
- name: SE_SESSIONS_MAP_PORT

tests/docker-compose-v3-test-parallel.yml

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ services:
3838
- SE_VIDEO_FILE_NAME=auto
3939
- SE_SERVER_PROTOCOL=https
4040
- SE_NODE_GRID_URL=https://selenium-hub:4444
41+
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
4142
restart: always
4243

4344
firefox:
@@ -73,6 +74,7 @@ services:
7374
- SE_VIDEO_FILE_NAME=auto
7475
- SE_SERVER_PROTOCOL=https
7576
- SE_NODE_GRID_URL=https://selenium-hub:4444
77+
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
7678
restart: always
7779

7880
edge:
@@ -106,6 +108,7 @@ services:
106108
- SE_VIDEO_FILE_NAME=auto
107109
- SE_SERVER_PROTOCOL=https
108110
- SE_NODE_GRID_URL=https://selenium-hub:4444
111+
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
109112
restart: always
110113

111114
selenium-hub:

tests/docker-compose-v3-test-standalone.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
- SE_ROUTER_USERNAME=${BASIC_AUTH_USERNAME}
2121
- SE_ROUTER_PASSWORD=${BASIC_AUTH_PASSWORD}
2222
- SE_SUB_PATH=${SUB_PATH}
23+
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
2324
ports:
2425
- "4444:4444"
2526
healthcheck:

0 commit comments

Comments
 (0)