Resource library

QA How-To

Running tests on a Selenium Grid in Kubernetes (2026)

Run tests on a Selenium Grid in Kubernetes: Helm deploy, Remote WebDriver, scaling, readiness, NetworkPolicy security, CI patterns, and debugging.

19 min read | 2,691 words

TL;DR

Running tests on a Selenium Grid in Kubernetes means deploying a private Grid, pointing Remote WebDriver at the Service URL, sizing browser pods correctly, waiting for real readiness, scaling slots deliberately, and securing access. Always quit sessions and align CI parallelism with capacity.

Key Takeaways

  • Use Service DNS, not localhost, for in-cluster Remote WebDriver clients.
  • Pin Selenium images and size Chrome pods with realistic memory and shm.
  • Gate CI on Grid readiness and registered browser capabilities.
  • Scale node replicas and bound runner concurrency to free slots.
  • Keep the Grid private with auth and NetworkPolicies.
  • Always quit sessions; timeouts are recovery, not normal cleanup.
  • Choose ephemeral per-pipeline Grids when shared tenancy causes noise.

Running tests on a Selenium Grid in Kubernetes is how many teams scale browser automation beyond a single Docker Compose host. In 2026, the common pattern is a Kubernetes-native Grid (official Helm charts or equivalent manifests), Remote WebDriver clients in CI jobs, tight resource requests for browser nodes, and strict network policy so the Grid is never a public drive-by browser farm.

This guide focuses on the QA and SDET operator view: deploy a small Grid, prove a session, scale nodes, wire CI, collect evidence, and debug the failures that only appear under orchestration. For local Docker fundamentals first, read Docker for Selenium Grid and Docker basics for testers.

TL;DR

Layer Practical starting point
Cluster Dedicated namespace, non-public ingress
Grid topology Hub/Router entry plus browser node deployments
Packaging Official Selenium Docker images via Helm or manifests
Client Remote WebDriver to the Grid service URL
Capacity One browser session per adequately resourced pod initially
CI Job waits for readiness, runs suite, always quits sessions
Security Private network, auth at ingress, no anonymous internet exposure

Running tests on a Selenium Grid in Kubernetes succeeds when capacity, readiness, and session teardown are treated as first-class test infrastructure concerns.

1. Why Kubernetes for Selenium Grid

Compose is excellent for a laptop or a single CI agent. Kubernetes becomes attractive when you need:

  • Elastic browser capacity across many parallel pipelines.
  • Multi-team isolation via namespaces and quotas.
  • Standardized deployment, rollouts, and health probes.
  • Integration with cluster observability and secrets.

Trade-offs are real: you inherit cluster networking, DNS, storage classes, and RBAC complexity. If your suite needs only two concurrent Chrome sessions, Compose may still be the better tool. Choose Kubernetes when queue time and host sprawl already hurt.

Approach Strength Watch-out
Local browsers Simplest debugging Weak parallelism
Docker Compose Grid Fast to learn Limited multi-tenant scale
Kubernetes Grid Elastic, multi-team Ops and networking skill required
Vendor cloud browsers Managed capacity Cost, data residency, lock-in

2. Core Architecture Inside the Cluster

A Selenium 4 Grid routes new sessions to nodes that advertise browser capabilities. In Kubernetes terms:

  1. A Service exposes the Grid entry point (often port 4444) inside the cluster.
  2. Browser node Deployments register with the Grid event bus or discovery mechanism used by your chart.
  3. Test pods or external CI agents open Remote WebDriver sessions against the Service DNS name.
  4. Each session occupies a slot until quit() or timeout.

DNS names such as selenium-router.selenium.svc.cluster.local replace localhost when tests run in-cluster. When tests run on GitHub-hosted runners outside the cluster, you need a secure tunnel, internal runner, or authenticated ingress. Do not open an unauthenticated LoadBalancer to the world.

Resource design starts from browser reality: Chromium is memory hungry. Undersized pods produce crashes that masquerade as flaky tests.

3. Namespace, Quotas, and Image Hygiene

Create a dedicated namespace:

kubectl create namespace selenium
kubectl label namespace selenium app.kubernetes.io/name=selenium-grid

Apply resource quotas so a runaway scale event cannot starve the cluster:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: selenium-quota
  namespace: selenium
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    pods: "40"

Pin image tags for Hub and nodes to the same reviewed Selenium release family. Avoid latest. Scan images in your registry mirror if policy requires internal-only pulls.

4. Deploy a Minimal Grid With Helm or Manifests

Many teams install the official Selenium Grid Helm chart. Illustrative flow (confirm chart values against current chart docs for your version):

helm repo add docker-selenium https://www.selenium.dev/docker-selenium
helm repo update
helm upgrade --install selenium docker-selenium/selenium-grid \
  --namespace selenium \
  --set isolateComponents=false \
  --set chromeNode.replicas=2 \
  --set firefoxNode.replicas=1 \
  --set chromeNode.resources.requests.memory=2Gi \
  --set chromeNode.resources.limits.memory=3Gi

If you manage raw manifests, express nodes as Deployments with:

  • shm via an emptyDir mounted at /dev/shm with medium Memory when appropriate.
  • Liveness/readiness probes aligned with Grid health endpoints.
  • Anti-affinity so browser pods spread across nodes when possible.

Verify:

kubectl -n selenium get pods
kubectl -n selenium get svc
kubectl -n selenium port-forward svc/selenium-router 4444:4444
curl -sS http://127.0.0.1:4444/status | head

Port-forwarding is for operator debugging, not a production access pattern for all CI.

5. Run a Remote WebDriver Client Against the Grid

Python smoke test suitable for an in-cluster job or local port-forward:

import os
import sys

from selenium import webdriver
from selenium.webdriver.common.by import By

grid_url = os.environ.get("SELENIUM_GRID_URL", "http://127.0.0.1:4444")
browser = os.environ.get("BROWSER", "chrome").lower()

if browser == "firefox":
    options = webdriver.FirefoxOptions()
else:
    options = webdriver.ChromeOptions()

options.set_capability("se:name", "k8s grid smoke")

driver = webdriver.Remote(command_executor=grid_url, options=options)
try:
    driver.get("https://example.com")
    heading = driver.find_element(By.CSS_SELECTOR, "h1")
    assert heading.text == "Example Domain"
    print("OK", browser)
finally:
    driver.quit()

Run:

export SELENIUM_GRID_URL=http://127.0.0.1:4444
python grid_k8s_smoke.py

Always quit sessions. Abandoned sessions in Kubernetes still hold slots until timeout and create artificial queueing. Framework fixtures should guarantee teardown even on assertion failures.

6. Scale Nodes and Align Runner Concurrency

Scale Chrome nodes:

kubectl -n selenium scale deployment/selenium-chrome-node --replicas=5

(Exact deployment names depend on your chart release.) Capacity planning formula:

useful concurrency ≈ matching free slots
runner threads should not chronically exceed useful concurrency

If CI launches 20 parallel jobs against 5 Chrome slots, most time is queue wait. Either raise node replicas (budget permitting) or bound job parallelism. Application environments also collapse under synthetic login storms; scale the system under test or isolate tenants.

Symptom Likely cause First check
Long new session times Slot exhaustion /status, replica counts
Browser crash mid-test Memory or shm pod events, OOMKilled
SessionNotCreated Capability mismatch requested browserName vs nodes
Random disconnects Node eviction / rollout deployment events, pod age

7. Readiness, Probes, and Safe Rollouts

Kubernetes readiness must reflect Grid usefulness, not only process start. If probes pass before nodes register, CI will flake on session creation. After deploys:

  1. Wait for pods Ready.
  2. Poll Grid /status until ready and required browsers appear.
  3. Optionally run a one-shot session probe job.

During chart upgrades, prefer rolling strategies that do not kill all browser pods simultaneously. Drain or wait for active sessions when your operational model supports it. For CI-ephemeral Grids, create a fresh release namespace per pipeline and delete it at the end instead of sharing a mutating long-lived Grid.

Ephemeral per-pipeline Grids reduce noisy neighbor issues and simplify teardown:

helm upgrade --install "selenium-${CI_RUN_ID}" docker-selenium/selenium-grid \
  --namespace "selenium-${CI_RUN_ID}" --create-namespace ...
# run tests
helm uninstall "selenium-${CI_RUN_ID}" --namespace "selenium-${CI_RUN_ID}"
kubectl delete namespace "selenium-${CI_RUN_ID}"

8. Networking, Ingress, and Security

Hard rules for running tests on a Selenium Grid in Kubernetes:

  • Keep the Grid Service private by default.
  • If ingress is required, enforce authentication and IP allow lists.
  • Use NetworkPolicies so only test namespaces can reach port 4444.
  • Store registry pull secrets and test credentials in Kubernetes Secrets or an external manager.
  • Do not mount the host Docker socket into random test pods.

Example NetworkPolicy sketch (adjust selectors to your labels):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-tests-to-grid
  namespace: selenium
spec:
  podSelector:
    matchLabels:
      app: selenium-router
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              purpose: ci-tests
      ports:
        - protocol: TCP
          port: 4444

A Grid can navigate to internal admin UIs. Treat it like remote code execution with a browser UI.

9. CI Integration Patterns

In-cluster tests: a Kubernetes Job uses the in-cluster Service DNS, runs pytest or TestNG, uploads artifacts to object storage, then exits.

External CI runners: use self-hosted runners on the cluster network, or a secure egress path. Port-forward from SaaS runners is fragile for large suites.

GitHub Actions sketch with a self-hosted runner label:

jobs:
  ui:
    runs-on: [self-hosted, k8s]
    steps:
      - uses: actions/checkout@v4
      - name: Run Selenium suite
        env:
          SELENIUM_GRID_URL: http://selenium-router.selenium.svc.cluster.local:4444
        run: |
          python -m pip install selenium==4.45.0 pytest
          pytest -q

Collect logs:

kubectl -n selenium logs deploy/selenium-router --tail=200
kubectl -n selenium get events --sort-by=.lastTimestamp | tail -n 50

Pair pipeline design with reusable GitHub Actions workflows for QA when many repos share the same Grid access pattern.

10. Observability and Failure Diagnosis

Layer signals:

  1. Client exceptions and timestamps.
  2. Grid status and UI.
  3. Kubernetes pod events and kubectl describe.
  4. Node browser logs.
  5. Cluster metrics (CPU throttle, memory, disk).

OOMKilled browser pods are not flaky tests. Raise memory requests/limits and confirm /dev/shm size. Evicted pods during cluster pressure need quotas and priority classes for interactive browser workloads.

Tracing can integrate with OpenTelemetry-centric stacks used by Selenium Grid at larger scale. Start with structured logs and status metrics; add deep tracing when multi-hop latency needs proof.

11. Cost Control and When Not to Use a Cluster Grid

Browsers are expensive continuous workloads. Control cost by:

  • Scaling node Deployments to zero off-hours if policy allows.
  • Using spot/preemptible nodes only for interruption-tolerant suites.
  • Separating smoke capacity from full regression capacity.
  • Deleting ephemeral namespaces aggressively.

Skip Kubernetes Grid when a managed browser cloud meets compliance needs cheaper, or when a single Compose service already meets SLA. Orchestration is a means, not a badge.

12. Autoscaling Strategies for Browser Nodes

Static replica counts are easy to reason about and often enough for business-hour CI. When load is spiky, consider:

  1. Scheduled scaling: CronJobs or cluster autoscaling schedules raise Chrome replicas before the nightly regression window and scale down afterward.
  2. Queue-based scaling: Controllers watch Grid queue depth or CI concurrency metrics and adjust Deployments. Only invest here after basic metrics exist.
  3. Cluster autoscaler interaction: Browser pods must request enough resources to trigger node scale-up; tiny requests pack too densely and then OOM.

Avoid thrashing: cool-down periods matter. A Grid that scales up and down every minute will drop sessions during node termination unless you carefully drain.

Illustrative patch for a daytime replica target:

kubectl -n selenium scale deployment/selenium-chrome-node --replicas=8
# after nightly window
kubectl -n selenium scale deployment/selenium-chrome-node --replicas=2

Record the schedule in the same repository as the Helm values so operators are not the only memory of capacity.

13. Test Data, Sticky Sessions, and Stateful Side Effects

Grid routing is about browsers, not about your application tenant isolation. Parallel sessions that share one test user will collide on shopping carts, MFA state, or feature tours. Before blaming Kubernetes, ensure each session uses isolated identities or disposable tenants.

Sticky concerns:

  • File downloads should land in unique per-session directories or object storage keys.
  • Browser profile state should not be reused across tests unless intentional.
  • Video sidecars, if enabled, need unique names and storage class performance that keeps up with encoding.

When tests navigate to internal admin tools, confirm NetworkPolicies allow the browser pods to reach those URLs while still blocking the public internet from reaching the Grid. Egress and ingress are different questions.

14. Disaster Recovery and Version Skew Drills

Practice failure modes:

  1. Delete a Chrome node pod mid-suite and confirm the client fails clearly.
  2. Deploy mismatched client and Grid versions in a sandbox and capture the error signatures.
  3. Fill the namespace to its ResourceQuota and observe new session behavior.
  4. Rotate the ingress credential and ensure CI secrets update through your secret manager, not chat messages.

Document the on-call path: which dashboard shows queue depth, which command gathers logs, which Slack channel owns the Grid. Running tests on a Selenium Grid in Kubernetes is only sustainable when ownership is explicit.

Keep a known-good Helm values file tagged with the Selenium image digest. During an incident, redeploying known-good beats improvising image tags from memory.

15. Storage, Downloads, and Browser Artifacts on Kubernetes

Browser tests that download files need writable volumes. EmptyDir works for short jobs; PersistentVolumeClaims may be required when multiple steps inspect the same download. For parallel sessions, never share one writable directory without unique subpaths per session id.

Video and trace storage can overwhelm disks. Prefer uploading to object storage from a sidecar or a post-step, then discarding local files. Set pod ephemeral-storage requests when your cluster enforces them so nodes are not filled by accidental verbose recordings.

ConfigMap and Secret mounts can supply browser policies or enterprise certificates. Mount certificates read-only and rebuild node images when OS packages must change rather than live-patching containers.

Example emptyDir for shared memory style patterns (illustrative):

volumes:
  - name: dshm
    emptyDir:
      medium: Memory
      sizeLimit: 2Gi
volumeMounts:
  - name: dshm
    mountPath: /dev/shm

Confirm the chart you use already sets shm correctly before inventing duplicate mounts.

16. Multi-Team Isolation Models

Three common models for running tests on a Selenium Grid in Kubernetes:

Model Description Best when
Shared Grid namespace One Grid, many CI jobs Small org, strong quotas
Grid per team namespace Team-owned Helm release Different browser mixes
Grid per pipeline Ephemeral release each run Max isolation, higher cost

Shared grids need fairness: limit max concurrent sessions per team through separate queues, admission controllers, or simply separate node pools with dedicated Deployments labeled by team. Without fairness, one noisy regression suite starves smoke tests for everyone.

Name conventions help: selenium-team-payments, selenium-team-growth. DNS becomes obvious, NetworkPolicies become simpler, and chargeback metrics can attribute CPU to the right product.

Sample End-to-End CI Job Pseudoworkflow

  1. Create or select target namespace.
  2. helm upgrade --install with pinned chart and image digests.
  3. Wait for Deployments available and /status ready.
  4. Export SELENIUM_GRID_URL for the test job.
  5. Run pytest or TestNG with bounded parallelism.
  6. On completion, collect JUnit, screenshots, and Grid logs.
  7. Uninstall Helm release if ephemeral.
  8. Fail the pipeline if tests failed, even when cleanup succeeds.
set +e
pytest -q -n 4
status=$?
kubectl -n "$NS" logs deploy/selenium-router --tail=500 > router.log
helm uninstall "$RELEASE" -n "$NS" || true
exit $status

That cleanup pattern matches the discipline you already use on Compose Grids, translated to cluster APIs. Teaching new SDETs this sequence is more valuable than showing only the happy-path kubectl apply.

Linking Grid Capacity to Quality Gates

A green deploy gate that depends on UI tests only works if the Grid can absorb the required concurrency during release windows. Coordinate with release managers: freeze chart upgrades during the release train, pre-scale Chrome nodes, and pause nonessential nightlies that compete for slots.

Quality gates should distinguish infrastructure failure from product failure. If session creation fails for all tests, fail with an infrastructure status and page the Grid owners. If a single assertion fails after a healthy session, fail as product. Mixed signals waste time for both groups.

Running tests on a Selenium Grid in Kubernetes is therefore also an organizational design: clear owners, capacity forecasts, and runbooks sit beside Helm values. When those pieces exist, Remote WebDriver on Kubernetes becomes a boring, scalable default for browser coverage.

Client Timeouts Versus Infrastructure Timeouts

Map every timeout deliberately:

  • HTTP client connect and read timeouts for WebDriver commands.
  • New session queue timeout on the Grid.
  • Kubernetes probe timeouts and restart policies.
  • CI job timeout-minutes.

If the CI job dies first, you lose evidence. If the session queue is shorter than browser cold start under load, you get false capacity alarms. Document the hierarchy so on-call engineers know which layer aborted the run when running tests on a Selenium Grid in Kubernetes.

Interview Questions and Answers

Q: How do you run Selenium tests against a Grid on Kubernetes?

Deploy Grid components into a namespace, expose a ClusterIP Service, point Remote WebDriver at that Service URL from CI, size browser pods realistically, and always quit sessions. Readiness checks must ensure nodes are registered before the suite starts.

Q: Why can localhost:4444 fail from a test pod?

Localhost inside a pod is the pod itself, not the Grid. Use the Kubernetes Service DNS name or an injected environment variable that points at the Grid Service.

Q: How do you scale browser capacity?

Increase node Deployment replicas for the required browser, confirm registration in /status, and align test parallelism with free slots. Memory and CPU requests must rise with concurrency.

Q: What does OOMKilled mean for a Chrome node pod?

The container exceeded its memory limit and the kernel killed it. Increase limits/requests, check shm, reduce tabs or session density, and re-run with metrics.

Q: How do you secure a Selenium Grid in Kubernetes?

Private Services, authenticated ingress if needed, NetworkPolicies, least-privilege RBAC, scanned images, and no public anonymous access. Treat the Grid as sensitive infrastructure.

Q: When do you prefer ephemeral Grids per pipeline?

When teams interfere with each other on a shared Grid, or when you want clean teardown and version pinning per run. Cost is higher, isolation is better.

Q: How is Kubernetes Grid different from Docker Compose Grid?

Kubernetes adds scheduling, service discovery, rolling updates, quotas, and multi-node capacity. It also adds operational complexity. Compose remains ideal for small local or single-agent setups.

Q: What is your triage order for SessionNotCreatedException?

Check Grid readiness and registered capabilities, compare requested browserName, inspect node pod logs and events, verify resource health, then inspect client URL and network policies.

Common Mistakes

  • Exposing an unauthenticated Grid LoadBalancer to the internet.
  • Using localhost from in-cluster tests.
  • Tiny memory limits on Chrome pods.
  • Scaling replicas without raising cluster quotas.
  • Starting tests before nodes register.
  • Leaving sessions open until idle timeout.
  • Sharing one undersized Grid across every pipeline without queue metrics.
  • Ignoring NetworkPolicies and wondering why DNS resolves but TCP fails.
  • Rolling updates that kill all browser pods mid-suite.
  • Treating every timeout as application flakiness.

Conclusion

Running tests on a Selenium Grid in Kubernetes is an infrastructure design problem wrapped around Remote WebDriver. Pin images, size browser pods honestly, gate on real readiness, secure the entry point, align concurrency with slots, and tear down sessions and ephemeral releases cleanly.

Next step: deploy a two-replica Chrome Grid in a non-prod cluster, port-forward for a local smoke test, then run the same client from an in-cluster Job using Service DNS. When that path is boringly reliable, wire it into CI and scale with data rather than hope.

Interview Questions and Answers

Describe how Selenium Grid works on Kubernetes.

Grid components run as pods behind Services. Browser nodes register slots, and clients create Remote WebDriver sessions through the entry Service. Kubernetes schedules pods, enforces resources, and provides DNS for discovery.

How do you prevent flaky session creation after deploy?

I wait for pods to be Ready and for Grid status to show the required browsers before starting tests. I also keep client new-session timeouts consistent with expected queue time.

How do you size browser node pods?

I set memory and CPU from measured browser usage, ensure adequate shared memory, start with one session per pod, and revise with metrics after representative suites run.

What security controls belong on a k8s Grid?

Private networking, authenticated ingress if needed, NetworkPolicies, least-privilege RBAC, pinned scanned images, and strict secret handling for any credentials used by tests.

How do you debug SessionNotCreatedException in cluster?

I compare capabilities with registered nodes, check router and node logs, inspect pod events for OOM or probe failures, and verify the client URL and network path.

When is Kubernetes the wrong choice for Grid?

When concurrency needs are small and Compose on a single agent meets SLAs. Kubernetes overhead is justified by elastic multi-team scale, not by novelty.

How should CI clean up Grid sessions?

Tests must call quit in finally blocks or fixtures. Infrastructure timeouts catch leaks, and ephemeral namespaces or jobs should terminate so pods and PVCs do not linger.

Frequently Asked Questions

How do I start running tests on a Selenium Grid in Kubernetes?

Deploy Grid components to a namespace with Helm or manifests, verify /status, point Remote WebDriver at the Service URL, run a smoke session, then integrate the same URL into CI with readiness waits and guaranteed quit().

What URL should tests use inside the cluster?

Use the Kubernetes Service DNS name and port for the Grid entry point, for example http://selenium-router.selenium.svc.cluster.local:4444. Localhost only works with port-forward from your machine.

How many Chrome sessions per pod should I configure?

Start with one session per adequately resourced browser pod. Increase density only after measuring CPU, memory, stability, and application capacity.

Why are my Selenium pods OOMKilled?

Browser processes exceeded the container memory limit. Raise requests/limits, verify /dev/shm, reduce concurrency, and inspect kubectl describe events for OOMKilled.

Is it safe to expose Selenium Grid on a public load balancer?

No. An open Grid can drive browsers toward internal systems. Keep Services private and require authentication and network restrictions if external access is unavoidable.

How do I scale Selenium Grid on Kubernetes?

Scale browser node Deployments for the needed browser, confirm registration in Grid status, and ensure ResourceQuotas and node capacity can absorb the new pods.

Should each CI pipeline get its own Grid?

Ephemeral Grids improve isolation and simplify cleanup at higher cost. Shared Grids are efficient when you enforce quotas, queue metrics, and multi-tenant fairness.

Related Guides