diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a18a18563..438bf8278 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -53,7 +53,7 @@ jobs: - name: Set Selenium base version if: contains(toJson(github.event.commits), '[deploy]') == false run: | - make set_nightly_env + make set_build_nightly cat .env | xargs -I {} echo {} >> $GITHUB_ENV - name: Sets build date run: | diff --git a/.github/workflows/test-video.yml b/.github/workflows/docker-test.yml similarity index 90% rename from .github/workflows/test-video.yml rename to .github/workflows/docker-test.yml index 4613abc5c..15c0febc8 100644 --- a/.github/workflows/test-video.yml +++ b/.github/workflows/docker-test.yml @@ -23,8 +23,6 @@ on: pull_request: paths-ignore: - '**.md' - schedule: - - cron: '0 0 * * *' permissions: contents: read @@ -45,6 +43,8 @@ jobs: test-video: true - test-strategy: test_parallel test-video: false + - test-strategy: test_node_relay + test-video: false steps: - uses: actions/checkout@main - name: Set up QEMU @@ -61,6 +61,12 @@ jobs: with: python-version: '3.11' check-latest: true + - name: Enable KVM + if: matrix.test-strategy == 'test_node_relay' + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - name: Get branch name (only for push to branch) if: github.event_name == 'push' run: echo "BRANCH=$(echo ${PUSH_BRANCH##*/})" >> $GITHUB_ENV @@ -76,7 +82,7 @@ jobs: - name: Set Selenium base version if: contains(toJson(github.event.commits), '[deploy]') == false run: | - make set_nightly_env + make set_build_nightly cat .env | xargs -I {} echo {} >> $GITHUB_ENV - name: Sets build date run: | diff --git a/.github/workflows/helm-chart-test.yml b/.github/workflows/helm-chart-test.yml index 9c32a99cf..7d15e4917 100644 --- a/.github/workflows/helm-chart-test.yml +++ b/.github/workflows/helm-chart-test.yml @@ -23,8 +23,6 @@ on: description: 'Test parameter for different log level' required: false default: 'FINE' - schedule: - - cron: '0 0 * * *' permissions: contents: read @@ -111,7 +109,7 @@ jobs: - name: Set Selenium base version if: contains(toJson(github.event.commits), '[deploy]') == false run: | - make set_nightly_env + make set_build_nightly cat .env | xargs -I {} echo {} >> $GITHUB_ENV - name: Sets build date run: | diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 28cc79eb4..caa816a90 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -5,7 +5,16 @@ on: - cron: '0 1 * * *' jobs: + docker-test: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: ./.github/workflows/docker-test.yml + + helm-chart-test: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: ./.github/workflows/helm-chart-test.yml + deploy: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' name: Nightly build runs-on: ubuntu-latest permissions: write-all diff --git a/Makefile b/Makefile index 88469f048..0296ae48c 100644 --- a/Makefile +++ b/Makefile @@ -38,9 +38,10 @@ all: hub \ standalone_docker \ video -set_nightly_env: +set_build_nightly: echo BASE_VERSION=$(BASE_VERSION_NIGHTLY) > .env ; \ echo BASE_RELEASE=$(BASE_RELEASE_NIGHTLY) >> .env ; + echo "Execute 'source .env' to set the environment variables" docker_buildx_setup: sudo apt-get install --upgrade docker-buildx-plugin @@ -484,6 +485,27 @@ test_video: video hub chrome firefox edge done make test_video_integrity +test_node_relay: hub node_base standalone_firefox + sudo rm -rf ./tests/tests + for node in Android NodeFirefox ; do \ + cd ./tests || true ; \ + echo TAG=$(TAG_VERSION) > .env ; \ + echo LOG_LEVEL=$(or $(LOG_LEVEL), "INFO") >> .env ; \ + echo REQUEST_TIMEOUT=$(or $(REQUEST_TIMEOUT), 300) >> .env ; \ + echo SESSION_TIMEOUT=$(or $(SESSION_TIMEOUT), 300) >> .env ; \ + echo ANDROID_BASED_NAME=$(or $(ANDROID_BASED_NAME),budtmo) >> .env ; \ + echo ANDROID_BASED_IMAGE=$(or $(ANDROID_BASED_IMAGE),docker-android) >> .env ; \ + echo ANDROID_BASED_TAG=$(or $(ANDROID_BASED_TAG),emulator_14.0) >> .env ; \ + echo ANDROID_PLATFORM_API=$(or $(ANDROID_PLATFORM_API),14) >> .env ; \ + echo TEST_DELAY_AFTER_TEST=$(or $(TEST_DELAY_AFTER_TEST), 15) >> .env ; \ + echo NODE=$$node >> .env ; \ + echo TEST_NODE_RELAY=$$node >> .env ; \ + echo UID=$$(id -u) >> .env ; \ + echo BINDING_VERSION=$(BINDING_VERSION) >> .env ; \ + docker compose -f docker-compose-v3-test-node-relay.yml up --no-log-prefix --exit-code-from tests --build ; \ + if [ $$? -ne 0 ]; then exit 1; fi ; \ + done + test_node_docker: hub standalone_docker standalone_chrome standalone_firefox standalone_edge video sudo rm -rf ./tests/tests sudo rm -rf ./tests/videos; mkdir -p ./tests/videos/Downloads diff --git a/NodeBase/Dockerfile b/NodeBase/Dockerfile index 083822358..2967f4fe2 100644 --- a/NodeBase/Dockerfile +++ b/NodeBase/Dockerfile @@ -143,7 +143,7 @@ COPY --chown="${SEL_UID}:${SEL_GID}" start-selenium-node.sh \ start-xvfb.sh \ start-vnc.sh \ start-novnc.sh \ - generate_config /opt/bin/ + generate_config generate_relay_config /opt/bin/ # Selenium Grid logo as wallpaper for Fluxbox COPY selenium_grid_logo.png /usr/share/images/fluxbox/ubuntu-light.png diff --git a/NodeBase/generate_config b/NodeBase/generate_config index 4e5276666..aab92d856 100755 --- a/NodeBase/generate_config +++ b/NodeBase/generate_config @@ -48,24 +48,34 @@ echo "session-timeout = \"${SE_NODE_SESSION_TIMEOUT}\"" >> "$FILENAME" echo "override-max-sessions = ${SE_NODE_OVERRIDE_MAX_SESSIONS}" >> "$FILENAME" echo "detect-drivers = false" >> "$FILENAME" echo "drain-after-session-count = ${DRAIN_AFTER_SESSION_COUNT:-$SE_DRAIN_AFTER_SESSION_COUNT}" >> "$FILENAME" -echo "max-sessions = ${SE_NODE_MAX_SESSIONS} +# When node is handled both browser and relay, SE_NODE_MAX_CONCURRENCY is used to configure max concurrency based on sum of them +echo "max-sessions = ${SE_NODE_MAX_CONCURRENCY:-${SE_NODE_MAX_SESSIONS}} " >> "$FILENAME" -SE_NODE_BROWSER_NAME=$(cat /opt/selenium/browser_name) -SE_NODE_BROWSER_VERSION=$(short_version $(cat /opt/selenium/browser_version)) -SE__BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location) +if [ -f /opt/selenium/browser_name ]; then + SE_NODE_BROWSER_NAME=$(cat /opt/selenium/browser_name) +fi +if [ -f /opt/selenium/browser_version ]; then + SE_NODE_BROWSER_VERSION=$(short_version $(cat /opt/selenium/browser_version)) +fi +if [ -f /opt/selenium/browser_binary_location ]; then + SE__BROWSER_BINARY_LOCATION=$(cat /opt/selenium/browser_binary_location) +fi -if [[ -z "$SE_NODE_STEREOTYPE" ]]; then +# '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\": \"Linux\", ${SE__BROWSER_BINARY_LOCATION}}" else -SE_NODE_STEREOTYPE="$SE_NODE_STEREOTYPE" +SE_NODE_STEREOTYPE="${SE_NODE_STEREOTYPE}" fi -echo "[[node.driver-configuration]]" >> "$FILENAME" -echo "display-name = \"${SE_NODE_BROWSER_NAME}\"" >> "$FILENAME" -echo "stereotype = '${SE_NODE_STEREOTYPE}'" >> "$FILENAME" -echo "max-sessions = ${SE_NODE_MAX_SESSIONS} -" >> "$FILENAME" - +# 'stereotype' setting is mandatory +if [[ -n "${SE_NODE_STEREOTYPE}" ]]; then + echo "[[node.driver-configuration]]" >> "$FILENAME" + echo "display-name = \"${SE_NODE_BROWSER_NAME}\"" >> "$FILENAME" + echo "stereotype = '${SE_NODE_STEREOTYPE}'" >> "$FILENAME" + echo "max-sessions = ${SE_NODE_MAX_SESSIONS} + " >> "$FILENAME" +fi diff --git a/NodeBase/generate_relay_config b/NodeBase/generate_relay_config new file mode 100755 index 000000000..08284dbde --- /dev/null +++ b/NodeBase/generate_relay_config @@ -0,0 +1,24 @@ +#!/bin/bash + +if [[ -z "$CONFIG_FILE" ]]; then +FILENAME="/opt/selenium/config.toml" +else +FILENAME="$CONFIG_FILE" +fi + +if [[ -n "${SE_NODE_RELAY_URL}" ]]; then + echo "[relay]" >> "$FILENAME" + echo "url = \"${SE_NODE_RELAY_URL}\"" >> "$FILENAME" + if [[ -z "${SE_NODE_RELAY_STATUS_ENDPOINT}" ]]; then + echo "status-endpoint = \"/status\"" >> "$FILENAME" + else + echo "status-endpoint = \"${SE_NODE_RELAY_STATUS_ENDPOINT}\"" >> "$FILENAME" + fi + if [[ -n "${SE_NODE_RELAY_PROTOCOL_VERSION}" ]]; then + echo "protocol-version = \"${SE_NODE_RELAY_PROTOCOL_VERSION}\"" >> "$FILENAME" + fi + echo "configs = [ + \"${SE_NODE_RELAY_MAX_SESSIONS}\", \"{\\\"browserName\\\": \\\"${SE_NODE_RELAY_BROWSER_NAME}\\\", \\\"platformName\\\": \\\"${SE_NODE_RELAY_PLATFORM_NAME}\\\", \\\"appium:platformVersion\\\": \\\"${SE_NODE_RELAY_PLATFORM_VERSION}\\\"}\" + ] + " >> "$FILENAME" +fi diff --git a/NodeBase/start-selenium-node.sh b/NodeBase/start-selenium-node.sh index cf622f752..54cc493ed 100755 --- a/NodeBase/start-selenium-node.sh +++ b/NodeBase/start-selenium-node.sh @@ -98,6 +98,7 @@ fi if [ "$GENERATE_CONFIG" = true ]; then echo "Generating Selenium Config" /opt/bin/generate_config + /opt/bin/generate_relay_config fi EXTRA_LIBS="" diff --git a/README.md b/README.md index 5380ddc3c..4a2bf4a5b 100644 --- a/README.md +++ b/README.md @@ -940,6 +940,35 @@ $ docker run -d \ --shm-size="2g" selenium/node-chrome:4.20.0-20240505 ``` +### Node configuration relay commands + +Relaying commands to a service endpoint that supports WebDriver. +It is useful to connect an external service that supports WebDriver to Selenium Grid. An example of such service could be a cloud provider or an Appium server. +In this way, Grid can enable more coverage to platforms and versions not present locally. + +The following is an en example of configuration relay commands. + +[docker-compose-v3-test-node-relay.yml](tests/docker-compose-v3-test-node-relay.yml) + +If you want to relay commands only, `selenium/node-base` is suitable and lightweight for this purpose. +In case you want to configure node with both browsers and relay commands, respective node images can be used. + +To use environment variables for generate relay configs, set `SE_NODE_RELAY_URL` and other variables as below + +```toml +[relay] +url = "${SE_NODE_RELAY_URL}" +status-endpoint = "${SE_NODE_RELAY_STATUS_ENDPOINT}" +protocol-version = "${SE_NODE_RELAY_PROTOCOL_VERSION}" +configs = [ '${SE_NODE_RELAY_MAX_SESSIONS}', '{"browserName": "${SE_NODE_RELAY_BROWSER_NAME}", "platformName": "${SE_NODE_RELAY_PLATFORM_NAME}", "appium:platformVersion": "${SE_NODE_RELAY_PLATFORM_VERSION}"}' ] +``` + +To run a sample test with the relayed node, you can clone the project and try below command: + +```bash +make test_node_relay +``` + ### Setting Sub Path By default, Selenium is reachable at `http://127.0.0.1:4444/`. Selenium can be configured to use a custom subpath by specifying the `SE_SUB_PATH` diff --git a/Standalone/start-selenium-standalone.sh b/Standalone/start-selenium-standalone.sh index 33887459e..19c7f42a8 100755 --- a/Standalone/start-selenium-standalone.sh +++ b/Standalone/start-selenium-standalone.sh @@ -86,6 +86,7 @@ if [ ! -z "$SE_NEW_SESSION_THREAD_POOL_SIZE" ]; then fi /opt/bin/generate_config +/opt/bin/generate_relay_config echo "Selenium Grid Standalone configuration: " cat /opt/selenium/config.toml diff --git a/tests/Dockerfile.emulator b/tests/Dockerfile.emulator new file mode 100644 index 000000000..eb66cf15e --- /dev/null +++ b/tests/Dockerfile.emulator @@ -0,0 +1,13 @@ +ARG ANDROID_BASED_NAME +ARG ANDROID_BASED_IMAGE +ARG ANDROID_BASED_TAG +FROM ${ANDROID_BASED_NAME}/${ANDROID_BASED_IMAGE}:${ANDROID_BASED_TAG} AS android_based + +ARG CHROME_DRIVER_URL +# Download appium chromedriver +RUN curl ${CHROME_DRIVER_URL} -o /tmp/chromedriver.zip \ + && rm -rf ~/.appium/node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver/chromedriver/linux \ + && mkdir -p ~/.appium/node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver/chromedriver/linux \ + && unzip /tmp/chromedriver.zip -d ~/.appium/node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver/chromedriver/linux \ + && ~/.appium/node_modules/appium-uiautomator2-driver/node_modules/appium-chromedriver/chromedriver/linux/chromedriver --version \ + && rm -rf /tmp/chromedriver.zip diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index ee77aa49b..68c26ed54 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -21,10 +21,15 @@ WEB_DRIVER_WAIT_TIMEOUT = int(os.environ.get('WEB_DRIVER_WAIT_TIMEOUT', 60)) TEST_PARALLEL_HARDENING = os.environ.get('TEST_PARALLEL_HARDENING', 'false').lower() == 'true' TEST_DELAY_AFTER_TEST = int(os.environ.get('TEST_DELAY_AFTER_TEST', 0)) +TEST_NODE_RELAY = os.environ.get('TEST_NODE_RELAY', 'false') +TEST_ANDROID_PLATFORM_API = os.environ.get('ANDROID_PLATFORM_API') if SELENIUM_GRID_USERNAME and SELENIUM_GRID_PASSWORD: SELENIUM_GRID_HOST = f"{SELENIUM_GRID_USERNAME}:{SELENIUM_GRID_PASSWORD}@{SELENIUM_GRID_HOST}" +if TEST_NODE_RELAY == 'Android': + time.sleep(90) + class SeleniumGenericTests(unittest.TestCase): def test_title(self): @@ -126,6 +131,14 @@ def setUp(self): options.set_capability('se:screenResolution', '1920x1080') if SELENIUM_GRID_TEST_HEADLESS: options.add_argument('--headless=new') + if TEST_NODE_RELAY == 'Android': + options.set_capability('platformName', TEST_NODE_RELAY) + options.set_capability('appium:platformVersion', TEST_ANDROID_PLATFORM_API) + options.set_capability('appium:deviceName', 'emulator-5554') + options.set_capability('appium:automationName', 'uiautomator2') + options.set_capability('appium:browserName', 'chrome') + else: + options.set_capability('platformName', 'Linux') start_time = time.time() self.driver = webdriver.Remote( options=options, diff --git a/tests/docker-compose-v3-test-node-relay.yml b/tests/docker-compose-v3-test-node-relay.yml new file mode 100644 index 000000000..2a3de1936 --- /dev/null +++ b/tests/docker-compose-v3-test-node-relay.yml @@ -0,0 +1,94 @@ +version: "3" +services: + node-relay-emulator: + image: selenium/node-base:${TAG} + shm_size: 2gb + depends_on: + - selenium-hub + - appium-emulator + environment: + - SE_EVENT_BUS_HOST=selenium-hub + - SE_EVENT_BUS_PUBLISH_PORT=4442 + - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 + - SE_LOG_LEVEL=${LOG_LEVEL} + - SE_NODE_SESSION_TIMEOUT=${SESSION_TIMEOUT} + - SE_NODE_RELAY_URL=http://appium-emulator:4723 + - SE_NODE_RELAY_PROTOCOL_VERSION=HTTP/1.1 + - SE_NODE_RELAY_MAX_SESSIONS=1 + - SE_NODE_RELAY_PLATFORM_NAME=Android + - SE_NODE_RELAY_PLATFORM_VERSION=${ANDROID_PLATFORM_API} + - SE_NODE_RELAY_BROWSER_NAME=chrome + - SE_NODE_RELAY_WEB_VNC=ws://appium-emulator:6080/websockify + + node-relay-standalone: + image: selenium/node-base:${TAG} + shm_size: 2gb + depends_on: + - selenium-hub + - firefox-receiver + volumes: + - ./relay_config.toml:/opt/selenium/config.toml + environment: + - SE_EVENT_BUS_HOST=selenium-hub + - SE_EVENT_BUS_PUBLISH_PORT=4442 + - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 + - SE_LOG_LEVEL=${LOG_LEVEL} + - GENERATE_CONFIG=false + + selenium-hub: + image: selenium/hub:${TAG} + container_name: selenium-hub + environment: + - SE_LOG_LEVEL=${LOG_LEVEL} + - SE_SESSION_REQUEST_TIMEOUT=${REQUEST_TIMEOUT} + ports: + - "4442:4442" + - "4443:4443" + - "4444:4444" + + tests: + image: docker-selenium-tests:latest + build: + context: ./ + dockerfile: ./Dockerfile + depends_on: + - selenium-hub + environment: + - RUN_IN_DOCKER_COMPOSE=true + - SELENIUM_GRID_HOST=selenium-hub + - BINDING_VERSION=${BINDING_VERSION} + - SELENIUM_ENABLE_MANAGED_DOWNLOADS=false + - TEST_NODE_RELAY=${TEST_NODE_RELAY} + - ANDROID_PLATFORM_API=${ANDROID_PLATFORM_API} + - TEST_DELAY_AFTER_TEST=${TEST_DELAY_AFTER_TEST} + command: ["./bootstrap.sh", "${NODE}"] + + firefox-receiver: + image: selenium/standalone-firefox:${TAG} + shm_size: 2gb + container_name: firefox-receiver + + appium-emulator: + image: ${ANDROID_BASED_NAME}/${ANDROID_BASED_IMAGE}:latest + shm_size: 2gb + build: + args: + ANDROID_BASED_NAME: ${ANDROID_BASED_NAME} + ANDROID_BASED_IMAGE: ${ANDROID_BASED_IMAGE} + ANDROID_BASED_TAG: ${ANDROID_BASED_TAG} + CHROME_DRIVER_URL: https://chromedriver.storage.googleapis.com/113.0.5672.63/chromedriver_linux64.zip + dockerfile: ./Dockerfile.emulator + container_name: appium-emulator + environment: + - EMULATOR_DEVICE=Nexus 5 + - WEB_VNC=true + - APPIUM=true + - WEB_LOG=true + - WEB_LOG_PORT=9001 + - EMULATOR_NO_SKIN=true + - EMULATOR_NAME=emulator-5554 + devices: + - /dev/kvm + ports: + - "6080:6080" + - "4723:4723" diff --git a/tests/relay_config.toml b/tests/relay_config.toml new file mode 100755 index 000000000..ed847c972 --- /dev/null +++ b/tests/relay_config.toml @@ -0,0 +1,17 @@ +[events] +publish = "tcp://selenium-hub:4442" +subscribe = "tcp://selenium-hub:4443" + +[node] +session-timeout = "300" +override-max-sessions = false +detect-drivers = false +drain-after-session-count = 0 +max-sessions = 1 + +[relay] +url = "http://firefox-receiver:4444/wd/hub" +status-endpoint = "/status" +configs = [ + '1', '{"browserName":"firefox","platformName":"linux"}' +] diff --git a/tests/test.py b/tests/test.py index 02bf23c08..17add9228 100644 --- a/tests/test.py +++ b/tests/test.py @@ -44,6 +44,8 @@ } TEST_NAME_MAP = { + "Android": "ChromeTests", + # Chrome Images 'NodeChrome': 'ChromeTests', 'StandaloneChrome': 'ChromeTests',