SeleniumHQ / SeleniumHQ/docker-selenium

[🚀 Feature]: Automatic creation of new jobs with browsers

Open
#2,693 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs-triaging
Dominant language
Go
Stars
8.7k
Forks
2.5k
Avg merge
10h 16m
Merged PRs (30d)
20

Description

Feature and motivation

In my installation, I added another component for the automatic generation of jobs with browsers. And since you’ve already started building multiple browser images versions with the new Selenium, I’d like to share my implementation. It would be awesome if we could add similar logic to Selenium Grid.

#!/bin/bash
PVC_NAME=$(kubectl get pvc -n $SE_NAMESPACE -o jsonpath='{.items[0].metadata.name}')

function deployment_exists(){
  kubectl get scaledjob selenium-grid-selenium-${1,,}-node-v${2%.*} --namespace $SE_NAMESPACE > /dev/null 2>&1
}

while true
do
  readarray requests <<< "$(curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessionQueueRequests } }"}' -sfk $SE_NODE_GRID_GRAPHQL_URL | jq .data.sessionsInfo.sessionQueueRequests)"
  unset "requests[0]"
  if [[ ${#requests[*]} -gt 0 ]]; then
    unset "requests[-1]"
  fi
  for i in "${!requests[@]}"
    do
      name=$(echo -e ${requests[$i]} | grep browserName | awk -F'[:,]' '{for(i=1;i<=NF;i++){if($i~/browserName/){print $(i+1)}}}' | tr -d '[:space:]' | tr -d '\\"')
      version=$(echo -e ${requests[$i]} | grep browserVersion | awk -F'[:,]' '{for(i=1;i<=NF;i++){if($i~/browserVersion/){print $(i+1)}}}' | tr -d '[:space:]' | tr -d '\\"')
      platform=$(echo -e ${requests[$i]} | grep platformName | awk -F'[:,]' '{for(i=1;i<=NF;i++){if($i~/platformName/){print $(i+1)}}}' | tr -d '[:space:]' | tr -d '\\"')
      name=${name,,}
      platform=${platform,,}
      if ! [[ -z "$name" ]] && ! [[ -z "$version" ]] && [[ "$platform" == "linux" ]]; then
        pattern="\b[1-9][0-9][0-9]\.[0]\b"
        if [[ $version =~ $pattern ]]; then
          case $name in
            microsoftedge)
              if (( $(echo "$version < 120.0" |bc -l) )); then
                echo "This version of $name is no longer supported by our Grid. Version=$version"
              else
                deployments[$i]=edge:$version
              fi
              ;;
            chrome)
              if (( $(echo "$version < 120.0" |bc -l) )); then
                echo "This version of $name is no longer supported by our Grid. Version=$version"
              else
                deployments[$i]=chrome:$version
              fi
              ;;
            firefox)
              if (( $(echo "$version < 122.0" |bc -l) )); then
                echo "This version of $name is no longer supported by our Grid. Version=$version"
              else
                deployments[$i]=firefox:$version
              fi
              ;;
          esac
        else
          echo "Version of $name is incorrect, version=$version"
        fi
      fi
    done
  uniqs_deployments=($(for d in "${deployments[@]}"; do echo "${d}"; done | sort -u))
  for i in "${!uniqs_deployments[@]}"
    do
      name=$(echo ${uniqs_deployments[$i]} | cut -d ':' -f 1 )
      version=$(echo ${uniqs_deployments[$i]} | cut -d ':' -f 2 )
      if [ $name == "edge" ]; then kedabrowsername=MicrosoftEdge; sessionBrowserName=msedge; else kedabrowsername=$name; sessionBrowserName=$name; fi
      if [ $name == "firefox" ]; then disable_dshm=""; else disable_dshm="--disable-dev-shm-usage"; fi
      if [ $SE_NAMESPACE == "selenium-test" ]; then testTag="-test"; else testTag=""; fi
      if ! deployment_exists $name $version; then
        tags=$(curl -s -X GET https://registry.host/v2/$name/tags/list | jq -r '.tags[]')
        filtered_tags=$(echo "$tags" | grep "^$version")
        if [ "$filtered_tags" ]; then
          if [ -n "$testTag" ]; then
            filtered_tags=$(echo "$filtered_tags" | grep "\-test")
          else
            filtered_tags=$(echo "$filtered_tags" | grep -v "\-test")
          fi
          latest_tag=$(echo "$filtered_tags" | sort -V | tail -n 1)
          kubectl apply -f - <<EOF
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
  annotations:
    helm.sh/hook: post-install,post-upgrade,post-rollback
    helm.sh/hook-weight: "1"
    autoscaling.keda.sh/paused: "false"
  finalizers:
  - finalizer.keda.sh
  generation: 2
  labels:
    app: selenium-grid-selenium-$name-node-v${version%.*}
    app.kubernetes.io/instance: selenium-grid
    app.kubernetes.io/name: selenium-grid-selenium-$name-node-v${version%.*}
  name: selenium-grid-selenium-$name-node-v${version%.*}
  namespace: $SE_NAMESPACE
spec:
  failedJobsHistoryLimit: 10
  jobTargetRef:
    activeDeadlineSeconds: 3600
    backoffLimit: 0
    completions: 1
    parallelism: 1
    template:
      metadata:
        labels:
          app: selenium-grid-selenium-$name-node-v${version%.*}
          app.kubernetes.io/name: selenium-grid-selenium-$name-node-v${version%.*}
          app.kubernetes.io/instance: selenium-grid
      spec:
        containers:
        - env:
          - name: KUBERNETES_NODE_HOST_IP
            valueFrom:
              fieldRef:
                fieldPath: status.hostIP
          - name: SE_NODE_MAX_SESSIONS
            value: "1"
          - name: SE_DRAIN_AFTER_SESSION_COUNT
            value: "1"
          - name: SE_NODE_BROWSER_VERSION
            value: "$version"
          - name: SE_NODE_PLATFORM_NAME
            value: linux
          - name: SE_NODE_STEREOTYPE_EXTRA
          - name: SE_NODE_CONTAINER_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: SE_BROWSER_ARGS_DISABLE_DSHM
            value: $disable_dshm
          - name: SE_OTEL_SERVICE_NAME
            value: selenium-node-$(echo "$SE_SUB_PATH" | cut -c 2-).$name:$version
          - name: SE_ENABLE_TRACING
            value: "true"
          - name: SE_NODE_HOST
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
          - name: SE_NODE_PORT
            value: "5555"
          - name: SE_NODE_REGISTER_PERIOD
            value: "120"
          - name: SE_NODE_REGISTER_CYCLE
            value: "5"
          - name: SE_SESSION_REQUEST_TIMEOUT
            value: "3600"
          - name: SE_VNC_NO_PASSWORD
            value: "1"
          - name: SE_VNC_VIEW_ONLY
            value: "1"
          - name: SE_OPTS
            value: "--enable-managed-downloads true"
          envFrom:
          - configMapRef:
              name: selenium-grid-selenium-event-bus-config
          - configMapRef:
              name: selenium-grid-selenium-node-config
          - configMapRef:
              name: selenium-grid-selenium-logging-config
          - configMapRef:
              name: selenium-grid-selenium-server-config
          - secretRef:
              name: selenium-grid-selenium-secrets
          - secretRef:
              name: selenium-grid-selenium-basic-auth-secrets
          image: registry.host/$name:$latest_tag
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command:
                - bash
                - -c
                - /opt/bin/nodePreStop.sh >> /proc/1/fd/1
          name: selenium-grid-selenium-$name-node-v${version%.*}
          ports:
          - containerPort: 5555
            protocol: TCP
          resources:
            limits:
              cpu: "4"
              memory: 2Gi
              ephemeral-storage: 1Gi
            requests:
              cpu: "1"
              memory: 1Gi
              ephemeral-storage: 256Mi
          startupProbe:
            failureThreshold: 12
            httpGet:
              path: /status
              port: 5555
              scheme: ${SE_SERVER_PROTOCOL^^}
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 60
          volumeMounts:
          - name: dshm
            mountPath: /dev/shm
          - name: artifacts
            mountPath: /artifacts
          - mountPath: /opt/bin/nodeGridUrl.sh
            name: selenium-grid-selenium-node-config
            subPath: nodeGridUrl.sh
          - mountPath: /opt/bin/nodePreStop.sh
            name: selenium-grid-selenium-node-config
            subPath: nodePreStop.sh
          - mountPath: /opt/bin/nodeProbe.sh
            name: selenium-grid-selenium-node-config
            subPath: nodeProbe.sh
          - mountPath: /opt/selenium/secrets
            name: selenium-grid-selenium-tls-secret
            readOnly: true
        - env:
          - name: SE_NODE_MAX_SESSIONS
            value: "1"
          - name: SE_DRAIN_AFTER_SESSION_COUNT
            value: "1"
          - name: SE_NODE_PORT
            value: "5555"
          - name: DISPLAY_CONTAINER_NAME
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
          envFrom:
          - configMapRef:
              name: selenium-grid-selenium-event-bus-config
          - configMapRef:
              name: selenium-grid-selenium-node-config
          - configMapRef:
              name: selenium-grid-selenium-recorder-config
          - configMapRef:
              name: selenium-grid-selenium-server-config
          - secretRef:
              name: selenium-grid-selenium-basic-auth-secrets
          - secretRef:
              name: selenium-grid-selenium-secrets
          image: docker-proxy.host/selenium/video:$SE_FF_MPEG_IMAGE
          imagePullPolicy: IfNotPresent
          name: video
          ports:
          - containerPort: 9000
            protocol: TCP
          resources:
            limits:
              cpu: "1"
              memory: 1Gi
              ephemeral-storage: 300Mi
            requests:
              cpu: 100m
              memory: 128Mi
              ephemeral-storage: 128Mi
          volumeMounts:
          - name: dshm
            mountPath: /dev/shm
          - mountPath: /opt/selenium/upload.conf
            name: selenium-grid-selenium-secrets
            subPath: upload.conf
          - mountPath: /videos
            name: videos
        initContainers:
        - command:
          - bash
          - -c
          - '''true'''
          image: registry.host/$name:$latest_tag
          imagePullPolicy: IfNotPresent
          name: pre-puller-selenium-grid-selenium-node
          resources:
            limits:
              cpu: "4"
              memory: 2Gi
              ephemeral-storage: 1Gi
            requests:
              cpu: "1"
              memory: 1Gi
              ephemeral-storage: 10Mi
        - command:
          - bash
          - -c
          - '''true'''
          image: docker-proxy.host/selenium/video:$SE_FF_MPEG_IMAGE
          imagePullPolicy: IfNotPresent
          name: pre-puller-video
          resources:
            limits:
              cpu: "1"
              memory: 1Gi
              ephemeral-storage: 300Mi
            requests:
              cpu: 100m
              memory: 128Mi
              ephemeral-storage: 128Mi
        restartPolicy: Never
        serviceAccount: $SE_NAMESPACE-sa
        serviceAccountName: $SE_NAMESPACE-sa
        shareProcessNamespace: false
        terminationGracePeriodSeconds: 3600
        volumes:
        - configMap:
            defaultMode: 493
            name: selenium-grid-selenium-node-config
          name: selenium-grid-selenium-node-config
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 1Gi
        - name: selenium-grid-selenium-tls-secret
          secret:
            secretName: selenium-grid-selenium-tls-secret
        - name: artifacts
          persistentVolumeClaim:
            claimName: $PVC_NAME
        - emptyDir: {}
          name: videos
        - configMap:
            defaultMode: 493
            name: selenium-grid-selenium-recorder-config
          name: selenium-grid-selenium-recorder-config
        - configMap:
            defaultMode: 493
            name: selenium-grid-selenium-uploader-config
          name: selenium-grid-selenium-uploader-config
        - name: selenium-grid-selenium-secrets
          secret:
            secretName: selenium-grid-selenium-secrets
  maxReplicaCount: 220
  minReplicaCount: 0
  pollingInterval: 20
  rollout:
    strategy: gradual
  scalingStrategy:
    strategy: default
  successfulJobsHistoryLimit: 0
  triggers:
  - authenticationRef:
      name: selenium-grid-selenium-scaler-trigger-auth
    metadata:
      browserName: $kedabrowsername
      browserVersion: "$version"
      capabilities: ""
      nodeMaxSessions: "1"
      platformName: linux
      sessionBrowserName: $sessionBrowserName
      unsafeSsl: "true"
    type: selenium-grid
EOF
        else
          echo "This $name version is not available on registry, version=$version"
        fi
      fi
  done
    unset name
    unset version
    unset uniqs_deployments
    unset deployments
    unset requests
    unset kedabrowsername
    unset tracesexporter
  sleep 5
done
Usage example

If we add this logic to Selenium Grid, then we’ll be able to support all existing browser versions, as well as those that will be released later.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No repository file, test, or entry point is named; begin by comparing the proposed Bash loop with the existing Kubernetes/Helm Selenium Grid setup. Define the supported browser/version discovery and ScaledJob lifecycle first, then validate that queued requests create the intended jobs and unsupported versions are handled.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, docker, kubernetes
Domain
devops, distributed-systems, infrastructure
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.