Skip to content

Commit

Permalink
SE_NODE_STEREOTYPE_EXTRA to append custom capabilities to default Nod…
Browse files Browse the repository at this point in the history
…e stereotype (#2624)

* SE_NODE_STEREOTYPE_EXTRA to append custom capabilities to default Node stereotype
* Add test

---------

Signed-off-by: Viet Nguyen Duc <[email protected]>
  • Loading branch information
VietND96 authored Jan 31, 2025
1 parent a396474 commit 8848360
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 7 deletions.
23 changes: 22 additions & 1 deletion ENV_VARIABLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
| SE_HTTPS_PRIVATE_KEY | /opt/selenium/secrets/tls.key | | |
| SE_ENABLE_TRACING | true | | |
| SE_OTEL_EXPORTER_ENDPOINT | | | |
| SE_OTEL_SERVICE_NAME | selenium-standalone-docker | | |
| SE_OTEL_SERVICE_NAME | selenium-router | | |
| SE_OTEL_JVM_ARGS | | | |
| SE_OTEL_TRACES_EXPORTER | otlp | | |
| SE_OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED | true | | |
Expand Down Expand Up @@ -121,3 +121,24 @@
| SE_SUPERVISORD_START_RETRIES | 5 | | |
| SE_RECORD_AUDIO | false | Flag to enable recording the audio source (default is Pulse Audio input) | |
| SE_AUDIO_SOURCE | -f pulse -ac 2 -i default | FFmpeg arguments to record the audio source | |
| SE_BROWSER_BINARY_LOCATION | | | |
| SE_NODE_BROWSER_NAME | | | |
| SE_NODE_CONTAINER_NAME | | | |
| SE_NODE_HOST | | | |
| 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 | |
| SE_NODE_RELAY_BROWSER_NAME | | | |
| SE_NODE_RELAY_MAX_SESSIONS | | | |
| SE_NODE_RELAY_PLATFORM_NAME | | | |
| SE_NODE_RELAY_PLATFORM_VERSION | | | |
| SE_NODE_RELAY_PROTOCOL_VERSION | | | |
| SE_NODE_RELAY_STATUS_ENDPOINT | | | |
| SE_NODE_RELAY_URL | | | |
| SE_NODE_STEREOTYPE | | Capabilities in JSON string to overwrite the default Node stereotype | |
| SE_NODE_STEREOTYPE_EXTRA | | Extra capabilities in JSON string that wants to merge to the default Node stereotype | |
| SE_SESSIONS_MAP_EXTERNAL_HOSTNAME | | | |
| SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION | | | |
| SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD | | | |
| SE_SESSIONS_MAP_EXTERNAL_JDBC_URL | | | |
| SE_SESSIONS_MAP_EXTERNAL_JDBC_USER | | | |
| SE_SESSIONS_MAP_EXTERNAL_PORT | | | |
| SE_SESSIONS_MAP_EXTERNAL_SCHEME | | | |
3 changes: 2 additions & 1 deletion NodeBase/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ COPY --chown="${SEL_UID}:${SEL_GID}" start-selenium-node.sh \
start-xvfb.sh \
start-vnc.sh \
start-novnc.sh \
generate_config generate_relay_config /opt/bin/
generate_config generate_relay_config json_merge.py /opt/bin/
RUN chmod +x /opt/bin/*.sh /opt/bin/*.py /opt/bin/generate_*

# Selenium Grid logo as wallpaper for Fluxbox
COPY selenium_grid_logo.png /usr/share/images/fluxbox/ubuntu-light.png
Expand Down
12 changes: 11 additions & 1 deletion NodeBase/generate_config
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,23 @@ fi
if [ -f /opt/selenium/browser_binary_location ] && [ -z "${SE_BROWSER_BINARY_LOCATION}" ]; then
SE_BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location)
fi
SE_NODE_CONTAINER_NAME="${SE_NODE_CONTAINER_NAME:-$(hostname)}"

# 'browserName' is mandatory for default stereotype
if [[ -z "${SE_NODE_STEREOTYPE}" ]] && [[ -n "${SE_NODE_BROWSER_NAME}" ]]; then
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}\"}"
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)\"}"
else
SE_NODE_STEREOTYPE="${SE_NODE_STEREOTYPE}"
fi
if [[ -n "${SE_NODE_STEREOTYPE_EXTRA}" ]]; then
echo "Merging SE_NODE_STEREOTYPE_EXTRA=${SE_NODE_STEREOTYPE_EXTRA} to main stereotype"
SE_NODE_STEREOTYPE="$(python3 /opt/bin/json_merge.py "${SE_NODE_STEREOTYPE}" "${SE_NODE_STEREOTYPE_EXTRA}")"
if [[ $? -ne 0 ]]; then
echo "Failed to merge SE_NODE_STEREOTYPE_EXTRA. Please check the format of the JSON string. Keep using main stereotype."
else
echo "Merged stereotype: ${SE_NODE_STEREOTYPE}"
fi
fi

# 'stereotype' setting is mandatory
if [[ -n "${SE_NODE_STEREOTYPE}" ]]; then
Expand Down
20 changes: 20 additions & 0 deletions NodeBase/json_merge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
import sys

json_str1 = sys.argv[1]
json_str2 = sys.argv[2]

try:
# Parse JSON strings into dictionaries
dict1 = json.loads(json_str1)
dict2 = json.loads(json_str2)
# Merge dictionaries
merged_dict = {**dict1, **dict2}
# Convert merged dictionary back to JSON string
merged_json_str = json.dumps(merged_dict, separators=(',', ':'), ensure_ascii=True)
# Print the merged JSON string
print(merged_json_str)
except:
# Print the first JSON string if an error occurs
print(json_str1)
sys.exit(1)
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1112,10 +1112,61 @@ Here is an example with the default values of these environment variables:
$ docker run -d \
-e SE_EVENT_BUS_HOST=<event_bus_ip|event_bus_name> \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 -e SE_NODE_STEREOTYPE="{\"browserName\":\"${SE_NODE_BROWSER_NAME}\",\"browserVersion\":\"${SE_NODE_BROWSER_VERSION}\",\"platformName\": \"Linux\"}" \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
-e SE_NODE_STEREOTYPE="{\"browserName\":\"${SE_NODE_BROWSER_NAME}\", \"browserVersion\":\"${SE_NODE_BROWSER_VERSION}\", \"platformName\":\"${SE_NODE_PLATFORM_NAME}\"}" \
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
```
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:
```bash
$ docker run -d \
-e SE_EVENT_BUS_HOST=<event_bus_ip|event_bus_name> \
-e SE_EVENT_BUS_PUBLISH_PORT=4442 \
-e SE_EVENT_BUS_SUBSCRIBE_PORT=4443 \
-e SE_NODE_STEREOTYPE_EXTRA="{\"myApp:version\":\"beta\", \"myApp:publish:\":\"public\"}" \
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
```
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:
```python
options = ChromeOptions()
options.set_capability('myApp:version', 'beta')
options.set_capability('myApp:publish', 'public')
driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL)
```
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.
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.
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.
```yaml
env:
- name: SE_NODE_CONTAINER_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
```
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:
```bash
$ 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 \
--shm-size="2g" selenium/node-chrome:4.28.1-20250123
$ docker exec -i my-node-1 hostname
a6971f95bbab
```
```python
options = ChromeOptions()
options.set_capability('container:hostname', 'a6971f95bbab')
driver = webdriver.Remote(options=options, command_executor=SELENIUM_GRID_URL)
```
_Noted: Those above changes require new image tag where the changeset is included & released._
### Node configuration relay commands
Relaying commands to a service endpoint that supports WebDriver.
Expand Down
12 changes: 11 additions & 1 deletion Standalone/generate_config
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,22 @@ fi
if [ -f /opt/selenium/browser_binary_location ] && [ -z "${SE_BROWSER_BINARY_LOCATION}" ]; then
SE_BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location)
fi
SE_NODE_CONTAINER_NAME="${SE_NODE_CONTAINER_NAME:-$(hostname)}"

if [[ -z "$SE_NODE_STEREOTYPE" ]]; then
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}\"}"
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)\"}"
else
SE_NODE_STEREOTYPE="$SE_NODE_STEREOTYPE"
fi
if [[ -n "${SE_NODE_STEREOTYPE_EXTRA}" ]]; then
echo "Merging SE_NODE_STEREOTYPE_EXTRA=${SE_NODE_STEREOTYPE_EXTRA} to main stereotype"
SE_NODE_STEREOTYPE="$(python3 /opt/bin/json_merge.py "${SE_NODE_STEREOTYPE}" "${SE_NODE_STEREOTYPE_EXTRA}")"
if [[ $? -ne 0 ]]; then
echo "Failed to merge SE_NODE_STEREOTYPE_EXTRA. Please check the format of the JSON string. Keep using main stereotype."
else
echo "Merged stereotype: ${SE_NODE_STEREOTYPE}"
fi
fi

echo "[[node.driver-configuration]]" >>"$FILENAME"
echo "display-name = \"${SE_NODE_BROWSER_NAME}\"" >>"$FILENAME"
Expand Down
65 changes: 65 additions & 0 deletions scripts/generate_list_env_vars/description.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,68 @@
- name: SE_AUDIO_SOURCE
description: FFmpeg arguments to record the audio source
cli: ''
- name: SE_BROWSER_BINARY_LOCATION
description: ''
cli: ''
- name: SE_NODE_BROWSER_NAME
description: ''
cli: ''
- name: SE_NODE_CONTAINER_NAME
description: ''
cli: ''
- name: SE_NODE_HOST
description: ''
cli: ''
- name: SE_NODE_MAX_CONCURRENCY
description: When node is handled both browser and relay, SE_NODE_MAX_CONCURRENCY
is used to configure max concurrency based on sum of them
cli: ''
- name: SE_NODE_RELAY_BROWSER_NAME
description: ''
cli: ''
- name: SE_NODE_RELAY_MAX_SESSIONS
description: ''
cli: ''
- name: SE_NODE_RELAY_PLATFORM_NAME
description: ''
cli: ''
- name: SE_NODE_RELAY_PLATFORM_VERSION
description: ''
cli: ''
- name: SE_NODE_RELAY_PROTOCOL_VERSION
description: ''
cli: ''
- name: SE_NODE_RELAY_STATUS_ENDPOINT
description: ''
cli: ''
- name: SE_NODE_RELAY_URL
description: ''
cli: ''
- name: SE_NODE_STEREOTYPE
description: Capabilities in JSON string to overwrite the default Node stereotype
cli: ''
- name: SE_NODE_STEREOTYPE_EXTRA
description: Extra capabilities in JSON string that wants to merge to the default
Node stereotype
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_HOSTNAME
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_URL
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_USER
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_PORT
description: ''
cli: ''
- name: SE_SESSIONS_MAP_EXTERNAL_SCHEME
description: ''
cli: ''
2 changes: 1 addition & 1 deletion scripts/generate_list_env_vars/extract_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def extract_variables_from_shell_scripts(directory_path):
for root, _, files in os.walk(directory_path):
files.sort()
for file in files:
if file.endswith(".sh"):
if file.endswith(".sh") or file.startswith("generate_"):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r') as f:
Expand Down
44 changes: 43 additions & 1 deletion scripts/generate_list_env_vars/value.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
default: -f pulse -ac 2 -i default
- name: SE_BIND_HOST
default: 'false'
- name: SE_BROWSER_BINARY_LOCATION
default: ''
- name: SE_BROWSER_LEFTOVERS_INTERVAL_SECS
default: '3600'
- name: SE_BROWSER_LEFTOVERS_PROCESSES_SECS
Expand Down Expand Up @@ -72,8 +74,12 @@
default: '%Y-%m-%d %H:%M:%S,%3N'
- name: SE_NEW_SESSION_THREAD_POOL_SIZE
default: ''
- name: SE_NODE_BROWSER_NAME
default: ''
- name: SE_NODE_BROWSER_VERSION
default: stable
- name: SE_NODE_CONTAINER_NAME
default: ''
- name: SE_NODE_DOCKER_CONFIG_FILENAME
default: ''
- name: SE_NODE_ENABLE_CDP
Expand All @@ -88,6 +94,10 @@
default: ''
- name: SE_NODE_HEARTBEAT_PERIOD
default: '30'
- name: SE_NODE_HOST
default: ''
- name: SE_NODE_MAX_CONCURRENCY
default: ''
- name: SE_NODE_MAX_SESSIONS
default: '1'
- name: SE_NODE_OVERRIDE_MAX_SESSIONS
Expand All @@ -102,8 +112,26 @@
default: ''
- name: SE_NODE_REGISTER_PERIOD
default: ''
- name: SE_NODE_RELAY_BROWSER_NAME
default: ''
- name: SE_NODE_RELAY_MAX_SESSIONS
default: ''
- name: SE_NODE_RELAY_PLATFORM_NAME
default: ''
- name: SE_NODE_RELAY_PLATFORM_VERSION
default: ''
- name: SE_NODE_RELAY_PROTOCOL_VERSION
default: ''
- name: SE_NODE_RELAY_STATUS_ENDPOINT
default: ''
- name: SE_NODE_RELAY_URL
default: ''
- name: SE_NODE_SESSION_TIMEOUT
default: '300'
- name: SE_NODE_STEREOTYPE
default: ''
- name: SE_NODE_STEREOTYPE_EXTRA
default: ''
- name: SE_NO_VNC_PORT
default: '7900'
- name: SE_OFFLINE
Expand All @@ -117,7 +145,7 @@
- name: SE_OTEL_JVM_ARGS
default: ''
- name: SE_OTEL_SERVICE_NAME
default: selenium-standalone-docker
default: selenium-router
- name: SE_OTEL_TRACES_EXPORTER
default: otlp
- name: SE_PRESET
Expand Down Expand Up @@ -158,6 +186,20 @@
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_DATASTORE
default: 'false'
- name: SE_SESSIONS_MAP_EXTERNAL_HOSTNAME
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_IMPLEMENTATION
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_PASSWORD
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_URL
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_JDBC_USER
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_PORT
default: ''
- name: SE_SESSIONS_MAP_EXTERNAL_SCHEME
default: ''
- name: SE_SESSIONS_MAP_HOST
default: ''
- name: SE_SESSIONS_MAP_PORT
Expand Down
3 changes: 3 additions & 0 deletions tests/docker-compose-v3-test-parallel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ services:
- SE_VIDEO_FILE_NAME=auto
- SE_SERVER_PROTOCOL=https
- SE_NODE_GRID_URL=https://selenium-hub:4444
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
restart: always

firefox:
Expand Down Expand Up @@ -73,6 +74,7 @@ services:
- SE_VIDEO_FILE_NAME=auto
- SE_SERVER_PROTOCOL=https
- SE_NODE_GRID_URL=https://selenium-hub:4444
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
restart: always

edge:
Expand Down Expand Up @@ -106,6 +108,7 @@ services:
- SE_VIDEO_FILE_NAME=auto
- SE_SERVER_PROTOCOL=https
- SE_NODE_GRID_URL=https://selenium-hub:4444
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
restart: always

selenium-hub:
Expand Down
1 change: 1 addition & 0 deletions tests/docker-compose-v3-test-standalone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ services:
- SE_ROUTER_USERNAME=${BASIC_AUTH_USERNAME}
- SE_ROUTER_PASSWORD=${BASIC_AUTH_PASSWORD}
- SE_SUB_PATH=${SUB_PATH}
- SE_NODE_STEREOTYPE_EXTRA={"myApp:version":"beta","myApp:publish":"public"}
ports:
- "4444:4444"
healthcheck:
Expand Down

0 comments on commit 8848360

Please sign in to comment.