diff --git a/.github/workflows/helm-chart-test.yml b/.github/workflows/helm-chart-test.yml index cf8f8c439a..d67f9ce49f 100644 --- a/.github/workflows/helm-chart-test.yml +++ b/.github/workflows/helm-chart-test.yml @@ -9,8 +9,12 @@ on: - 'charts/selenium-grid/**' workflow_dispatch: +permissions: + contents: read + jobs: lint-test: + name: "Lint Tests with ct" runs-on: ubuntu-latest steps: - name: Checkout @@ -45,6 +49,67 @@ jobs: - name: Create kind cluster uses: helm/kind-action@v1.8.0 + with: + config: ./tests/kind-cluster-config.yaml - name: Run chart-testing (install) run: ct install --all --config tests/chart-test.yaml + + deploy-grid-selenium-tests: + name: "Run Selenium Tests on K8s" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + browser: [NodeChrome,NodeEdge,NodeFirefox] + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v3 + with: + version: v3.13.2 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + check-latest: true + + - name: Create kind cluster + uses: helm/kind-action@v1.8.0 + with: + config: ./tests/kind-cluster-config.yaml + + # 👋 Documentation link for Ingress Installation on kind k8s cluster https://kind.sigs.k8s.io/docs/user/ingress + - name: Install ingress-nginx on kind kubernetes cluster + run: | + kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml + kubectl wait --namespace ingress-nginx \ + --for=condition=ready pod \ + --selector=app.kubernetes.io/component=controller \ + --timeout=90s + + - name: Deploy Selenium Grid Chart + run: | + helm repo add kedacore https://kedacore.github.io/charts + helm repo update + helm dependency build charts/selenium-grid + helm upgrade --install selenium-grid -f ./tests/override-kind-auth-${{matrix.browser}}-values.yaml charts/selenium-grid --namespace selenium-grid-test --create-namespace + kubectl get ingress --all-namespaces + + - name: Verify Post Deployment Grid Health and k8s pods status + run: | + python ./tests/K8sSmokeTest.py "http://localhost" + kubectl get pods -n selenium-grid-test + kubectl get events -n selenium-grid-test + + - name: Run Selenium Tests Against Kubernetes + run: | + export SELENIUM_GRID_HOST=localhost + export SELENIUM_GRID_PORT=80 + ./tests/bootstrap.sh testonkubernetes.py ${{matrix.browser}} \ No newline at end of file diff --git a/.github/workflows/update-dev-beta-browser-images.yml b/.github/workflows/update-dev-beta-browser-images.yml index 6349a71a0d..a0127f8683 100644 --- a/.github/workflows/update-dev-beta-browser-images.yml +++ b/.github/workflows/update-dev-beta-browser-images.yml @@ -56,8 +56,8 @@ jobs: run: | export SKIP_BUILD=true export NAMESPACE=$NAME - VERSION=$CHANNEL ./tests/bootstrap.sh Node$BROWSER_CAPS - VERSION=$CHANNEL ./tests/bootstrap.sh Standalone$BROWSER_CAPS + VERSION=$CHANNEL ./tests/bootstrap.sh test.py Node$BROWSER_CAPS + VERSION=$CHANNEL ./tests/bootstrap.sh test.py Standalone$BROWSER_CAPS - name: Login Docker Hub run: docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" diff --git a/Makefile b/Makefile index 5d4590d8ed..e2edc3fdaf 100644 --- a/Makefile +++ b/Makefile @@ -315,22 +315,22 @@ test: test_chrome \ test_chrome: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeChrome + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py NodeChrome test_chrome_standalone: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneChrome + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py StandaloneChrome test_edge: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeEdge + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py NodeEdge test_edge_standalone: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneEdge + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py StandaloneEdge test_firefox: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh NodeFirefox + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py NodeFirefox test_firefox_standalone: - VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh StandaloneFirefox + VERSION=$(TAG_VERSION) NAMESPACE=$(NAMESPACE) ./tests/bootstrap.sh test.py StandaloneFirefox # This should run on its own CI job. There is no need to combine it with the other tests. # Its main purpose is to check that a video file was generated. diff --git a/charts/selenium-grid/README.md b/charts/selenium-grid/README.md index c912d206e2..bcd2ad492f 100644 --- a/charts/selenium-grid/README.md +++ b/charts/selenium-grid/README.md @@ -36,13 +36,13 @@ helm install selenium-grid --set ingress.hostname=selenium-grid.k8s.local docker Selenium Grid has the ability to autoscale browser nodes up/down based on the pending requests in the session queue. -To do this [KEDA](https://keda.sh/docs/2.10/scalers/selenium-grid-scaler/) is used. When enabling +To do this [KEDA](https://keda.sh/docs/2.12/scalers/selenium-grid-scaler/) is used. When enabling autoscaling using `autoscaling.enabling` KEDA is installed automatically. To instead use an existing installation of KEDA you can enable autoscaling with `autoscaling.enableWithExistingKEDA` instead. KEDA can scale either with -[deployments](https://keda.sh/docs/2.10/concepts/scaling-deployments/#scaling-of-deployments-and-statefulsets) -or [jobs](https://keda.sh/docs/2.10/concepts/scaling-jobs/) and the charts support both types. This +[deployments](https://keda.sh/docs/2.12/concepts/scaling-deployments/#scaling-of-deployments-and-statefulsets) +or [jobs](https://keda.sh/docs/2.12/concepts/scaling-jobs/) and the charts support both types. This chart support both modes. It is controlled with `autoscaling.scalingType` that can be set to either job (default) or deployment. diff --git a/tests/SeleniumTests/__init__.py b/tests/SeleniumTests/__init__.py index 2590fdfa11..fb6308308c 100644 --- a/tests/SeleniumTests/__init__.py +++ b/tests/SeleniumTests/__init__.py @@ -9,7 +9,7 @@ from selenium.webdriver.chrome.options import Options as ChromeOptions SELENIUM_GRID_HOST = os.environ.get('SELENIUM_GRID_HOST', 'localhost') - +SELENIUM_GRID_PORT = os.environ.get('SELENIUM_GRID_PORT', '4444') class SeleniumGenericTests(unittest.TestCase): @@ -70,14 +70,14 @@ class ChromeTests(SeleniumGenericTests): def setUp(self): self.driver = webdriver.Remote( options=ChromeOptions(), - command_executor="http://%s:4444" % SELENIUM_GRID_HOST + command_executor="http://%s:%s" % (SELENIUM_GRID_HOST,SELENIUM_GRID_PORT) ) class EdgeTests(SeleniumGenericTests): def setUp(self): self.driver = webdriver.Remote( options=EdgeOptions(), - command_executor="http://%s:4444" % SELENIUM_GRID_HOST + command_executor="http://%s:%s" % (SELENIUM_GRID_HOST,SELENIUM_GRID_PORT) ) @@ -85,7 +85,7 @@ class FirefoxTests(SeleniumGenericTests): def setUp(self): self.driver = webdriver.Remote( options=FirefoxOptions(), - command_executor="http://%s:4444" % SELENIUM_GRID_HOST + command_executor="http://%s:%s" % (SELENIUM_GRID_HOST,SELENIUM_GRID_PORT) ) def test_title_and_maximize_window(self): diff --git a/tests/SmokeTests/__init__.py b/tests/SmokeTests/__init__.py index a8cc484218..6a672d2afd 100644 --- a/tests/SmokeTests/__init__.py +++ b/tests/SmokeTests/__init__.py @@ -9,7 +9,7 @@ from urllib.request import urlopen SELENIUM_GRID_HOST = os.environ.get('SELENIUM_GRID_HOST', 'localhost') - +SELENIUM_GRID_PORT = os.environ.get('SELENIUM_GRID_PORT', '4444') class SmokeTests(unittest.TestCase): def smoke_test_container(self, port): @@ -35,4 +35,4 @@ def smoke_test_container(self, port): class GridTest(SmokeTests): def test_grid_is_up(self): - self.smoke_test_container(4444) + self.smoke_test_container('%s' % SELENIUM_GRID_PORT) diff --git a/tests/bootstrap.sh b/tests/bootstrap.sh index a210862fa3..390fe624a6 100755 --- a/tests/bootstrap.sh +++ b/tests/bootstrap.sh @@ -11,7 +11,7 @@ python -m pip install selenium==4.15.0 \ docker===6.1.3 \ | grep -v 'Requirement already satisfied' -python test.py $1 +python $1 $2 ret_code=$? if [ "${CI:-false}" = "false" ]; then diff --git a/tests/docker-compose-v3-test-video.yml b/tests/docker-compose-v3-test-video.yml index e80c56b92c..bd6c17b635 100644 --- a/tests/docker-compose-v3-test-video.yml +++ b/tests/docker-compose-v3-test-video.yml @@ -43,4 +43,4 @@ services: environment: - RUN_IN_DOCKER_COMPOSE=true - SELENIUM_GRID_HOST=selenium-hub - command: ["./bootstrap.sh", "${NODE}"] + command: ["./bootstrap.sh", "test.py", "${NODE}"] diff --git a/tests/kind-cluster-config.yaml b/tests/kind-cluster-config.yaml new file mode 100644 index 0000000000..f0bcacd1be --- /dev/null +++ b/tests/kind-cluster-config.yaml @@ -0,0 +1,18 @@ +# This Config is required for KIND cluster to enable ingress. Documented here https://kind.sigs.k8s.io/docs/user/ingress +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: +- role: control-plane + kubeadmConfigPatches: + - | + kind: InitConfiguration + nodeRegistration: + kubeletExtraArgs: + node-labels: "ingress-ready=true" + extraPortMappings: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP diff --git a/tests/override-kind-auth-NodeChrome-values.yaml b/tests/override-kind-auth-NodeChrome-values.yaml new file mode 100644 index 0000000000..e3628ac840 --- /dev/null +++ b/tests/override-kind-auth-NodeChrome-values.yaml @@ -0,0 +1,13 @@ +# This is used in Helm chart testing. This disables the basic auth on seleneium grid +# Basic auth settings for Selenium Grid +basicAuth: + # Enable or disable basic auth + enabled: false +# Configuration for edge nodes +edgeNode: + # Enable edge nodes + enabled: false +# Configuration for firefox nodes +firefoxNode: + # Enable firefox nodes + enabled: false diff --git a/tests/override-kind-auth-NodeEdge-values.yaml b/tests/override-kind-auth-NodeEdge-values.yaml new file mode 100644 index 0000000000..936745c23e --- /dev/null +++ b/tests/override-kind-auth-NodeEdge-values.yaml @@ -0,0 +1,13 @@ +# This is used in Helm chart testing. This disables the basic auth on seleneium grid +# Basic auth settings for Selenium Grid +basicAuth: + # Enable or disable basic auth + enabled: false +# Configuration for chrome nodes +chromeNode: + # Enable chrome nodes + enabled: false +# Configuration for firefox nodes +firefoxNode: + # Enable firefox nodes + enabled: false diff --git a/tests/override-kind-auth-NodeFirefox-values.yaml b/tests/override-kind-auth-NodeFirefox-values.yaml new file mode 100644 index 0000000000..d679cbfa73 --- /dev/null +++ b/tests/override-kind-auth-NodeFirefox-values.yaml @@ -0,0 +1,13 @@ +# This is used in Helm chart testing. This disables the basic auth on seleneium grid +# Basic auth settings for Selenium Grid +basicAuth: + # Enable or disable basic auth + enabled: false +# Configuration for chrome nodes +chromeNode: + # Enable chrome nodes + enabled: false +# Configuration for edge nodes +edgeNode: + # Enable edge nodes + enabled: false diff --git a/tests/testonkubernetes.py b/tests/testonkubernetes.py new file mode 100644 index 0000000000..ee12603565 --- /dev/null +++ b/tests/testonkubernetes.py @@ -0,0 +1,54 @@ +import logging +import sys +import unittest + +# LOGGING # +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +TEST_NAME_MAP = { + # Chrome Images + 'NodeChrome': 'ChromeTests', + 'StandaloneChrome': 'ChromeTests', + + # Edge Images + 'NodeEdge': 'EdgeTests', + 'StandaloneEdge': 'EdgeTests', + + # Firefox Images + 'NodeFirefox': 'FirefoxTests', + 'StandaloneFirefox': 'FirefoxTests', +} + + +if __name__ == '__main__': + # The container to test against + image = sys.argv[1] + + + try: + # Smoke tests + logger.info('*********** Running smoke tests %s Tests **********' % image) + image_class = "%sTest" % image + module = __import__('SmokeTests', fromlist='GridTest') + test_class = getattr(module, 'GridTest') + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + test_runner = unittest.TextTestRunner(verbosity=3) + failed = not test_runner.run(suite).wasSuccessful() + except Exception as e: + logger.fatal(e) + failed = True + + try: + # Run Selenium tests + logger.info('*********** Running Selenium tests %s Tests **********' % image) + test_class = getattr(__import__('SeleniumTests', fromlist=[TEST_NAME_MAP[image]]), TEST_NAME_MAP[image]) + suite = unittest.TestLoader().loadTestsFromTestCase(test_class) + test_runner = unittest.TextTestRunner(verbosity=3) + failed = not test_runner.run(suite).wasSuccessful() + except Exception as e: + logger.fatal(e) + failed = True + + if failed: + exit(1)