Skip to content

SE_NODE_STEREOTYPE_EXTRA to append custom capabilities to default Node stereotype #2624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 31, 2025
Merged
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
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
Loading