Docker & Kubernetes Test Deployment
Master containerized test deployment and orchestration for enterprise-scale test automation.
Docker Basics for Test Automation
Dockerfile for Test Container
# Dockerfile
FROM maven:3.9-eclipse-temurin-17
# Install Chrome
RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list
RUN apt-get update && apt-get install -y google-chrome-stable
# Install ChromeDriver
RUN CHROME_VERSION=$(google-chrome --version | awk '{print $3}' | cut -d'.' -f1) && \
wget -q "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_VERSION}" -O /tmp/version && \
DRIVER_VERSION=$(cat /tmp/version) && \
wget -q "https://chromedriver.storage.googleapis.com/${DRIVER_VERSION}/chromedriver_linux64.zip" && \
unzip chromedriver_linux64.zip -d /usr/local/bin && \
chmod +x /usr/local/bin/chromedriver
# Set working directory
WORKDIR /app
# Copy project files
COPY pom.xml .
COPY src ./src
# Run tests
CMD ["mvn", "clean", "test", "-Dheadless=true"]
Docker Compose for Local Testing
# docker-compose.yml
version: '3.8'
services:
selenium-hub:
image: selenium/hub:4.15.0
ports:
- "4444:4444"
environment:
- SE_SESSION_REQUEST_TIMEOUT=300
- SE_SESSION_RETRY_INTERVAL=5
- SE_NODE_MAX_SESSIONS=5
networks:
- selenium-grid
chrome-node:
image: selenium/node-chrome:4.15.0
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=3
volumes:
- /dev/shm:/dev/shm
networks:
- selenium-grid
deploy:
replicas: 3
firefox-node:
image: selenium/node-firefox:4.15.0
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_EVENT_BUS_PUBLISH_PORT=4442
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
- SE_NODE_MAX_SESSIONS=3
volumes:
- /dev/shm:/dev/shm
networks:
- selenium-grid
deploy:
replicas: 2
test-runner:
build: .
depends_on:
- selenium-hub
environment:
- SELENIUM_GRID_URL=http://selenium-hub:4444
- TEST_ENV=docker
networks:
- selenium-grid
networks:
selenium-grid:
driver: bridge
Kubernetes Deployment
Selenium Grid on Kubernetes
# selenium-hub-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: selenium-hub
labels:
app: selenium-hub
spec:
replicas: 1
selector:
matchLabels:
app: selenium-hub
template:
metadata:
labels:
app: selenium-hub
spec:
containers:
- name: selenium-hub
image: selenium/hub:4.15.0
ports:
- containerPort: 4444
- containerPort: 4442
- containerPort: 4443
env:
- name: SE_SESSION_REQUEST_TIMEOUT
value: "300"
- name: SE_NODE_MAX_SESSIONS
value: "5"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
---
apiVersion: v1
kind: Service
metadata:
name: selenium-hub
spec:
selector:
app: selenium-hub
ports:
- name: web
port: 4444
targetPort: 4444
- name: event-bus-publish
port: 4442
targetPort: 4442
- name: event-bus-subscribe
port: 4443
targetPort: 4443
type: ClusterIP
Chrome Node Deployment
# chrome-node-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: chrome-node
labels:
app: chrome-node
spec:
replicas: 5
selector:
matchLabels:
app: chrome-node
template:
metadata:
labels:
app: chrome-node
spec:
containers:
- name: chrome-node
image: selenium/node-chrome:4.15.0
env:
- name: SE_EVENT_BUS_HOST
value: "selenium-hub"
- name: SE_EVENT_BUS_PUBLISH_PORT
value: "4442"
- name: SE_EVENT_BUS_SUBSCRIBE_PORT
value: "4443"
- name: SE_NODE_MAX_SESSIONS
value: "3"
resources:
requests:
memory: "1Gi"
cpu: "1000m"
limits:
memory: "2Gi"
cpu: "2000m"
volumeMounts:
- name: dshm
mountPath: /dev/shm
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 2Gi
Test Job Configuration
# test-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: selenium-test-job
spec:
parallelism: 10
completions: 10
template:
spec:
containers:
- name: test-runner
image: your-registry/selenium-tests:latest
env:
- name: SELENIUM_GRID_URL
value: "http://selenium-hub:4444"
- name: TEST_SUITE
valueFrom:
configMapKeyRef:
name: test-config
key: suite
- name: PARALLEL_THREADS
value: "5"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
restartPolicy: Never
backoffLimit: 3
Auto-Scaling Configuration
Horizontal Pod Autoscaler
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: chrome-node-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: chrome-node
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
KEDA Scaling (Event-Driven)
# keda-scaledobject.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: selenium-scaler
spec:
scaleTargetRef:
name: chrome-node
minReplicaCount: 2
maxReplicaCount: 50
triggers:
- type: selenium-grid
metadata:
url: 'http://selenium-hub:4444/graphql'
browserName: 'chrome'
sessionBrowserName: 'chrome'
targetValue: '1'
Configuration Management
ConfigMap for Test Configuration
# test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
data:
suite: "regression"
testng.xml: |
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="K8s Suite" parallel="methods" thread-count="10">
<test name="Regression">
<packages>
<package name="com.example.tests.*"/>
</packages>
</test>
</suite>
application.properties: |
selenium.grid.url=http://selenium-hub:4444
app.base.url=https://app.example.com
test.timeout=30
screenshot.enabled=true
Secrets for Credentials
# test-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: test-credentials
type: Opaque
stringData:
username: "test.user@example.com"
password: "SecureP@ssw0rd"
api-key: "your-api-key"
Using Secrets in Tests
public class KubernetesTestBase {
@BeforeClass
public void setup() {
// Read from environment variables injected from secrets
String username = System.getenv("TEST_USERNAME");
String password = System.getenv("TEST_PASSWORD");
String apiKey = System.getenv("API_KEY");
// Use in tests
}
}
CI/CD Integration
GitHub Actions Workflow
# .github/workflows/k8s-tests.yml
name: Kubernetes E2E Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Configure kubectl
run: |
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=./kubeconfig
- name: Deploy Selenium Grid
run: |
kubectl apply -f k8s/selenium-hub-deployment.yaml
kubectl apply -f k8s/chrome-node-deployment.yaml
kubectl wait --for=condition=ready pod -l app=selenium-hub --timeout=300s
- name: Build and push test image
run: |
docker build -t ${{ secrets.DOCKER_REGISTRY }}/selenium-tests:${{ github.sha }} .
docker push ${{ secrets.DOCKER_REGISTRY }}/selenium-tests:${{ github.sha }}
- name: Run tests
run: |
kubectl create configmap test-config --from-file=testng.xml
kubectl create -f k8s/test-job.yaml
kubectl wait --for=condition=complete job/selenium-test-job --timeout=1800s
- name: Get test results
if: always()
run: |
kubectl logs job/selenium-test-job > test-output.log
kubectl cp $(kubectl get pod -l job-name=selenium-test-job -o jsonpath='{.items[0].metadata.name}'):/app/target/surefire-reports ./reports
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-reports
path: reports/
- name: Cleanup
if: always()
run: |
kubectl delete job selenium-test-job
kubectl delete configmap test-config
kubectl delete -f k8s/
Monitoring & Logging
Prometheus Metrics
# prometheus-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: selenium-grid
spec:
selector:
matchLabels:
app: selenium-hub
endpoints:
- port: web
path: /metrics
interval: 30s
Grafana Dashboard
{
"dashboard": {
"title": "Selenium Grid Metrics",
"panels": [
{
"title": "Active Sessions",
"targets": [
{
"expr": "selenium_grid_hub_sessions_active"
}
]
},
{
"title": "Queue Size",
"targets": [
{
"expr": "selenium_grid_hub_queue_size"
}
]
},
{
"title": "Node Availability",
"targets": [
{
"expr": "selenium_grid_node_total_slots"
}
]
}
]
}
}
Best Practices
- Resource Limits: Always set memory and CPU limits
- Health Checks: Implement liveness and readiness probes
- Scaling: Use HPA or KEDA for auto-scaling
- Storage: Use PersistentVolumes for test artifacts
- Networking: Use Services for stable endpoints
- Secrets: Never hardcode credentials
- Monitoring: Implement comprehensive observability
Key Takeaways
- Docker containers provide consistent test environments
- Kubernetes orchestrates distributed test execution
- Auto-scaling handles variable test loads
- ConfigMaps and Secrets manage configuration
- CI/CD pipelines automate deployment
- Monitoring ensures system health
Practice Exercise
Deploy a complete Selenium Grid on Kubernetes:
- Create Docker images for your tests
- Deploy Selenium Hub and nodes to Kubernetes
- Configure HPA for auto-scaling
- Implement test job with parallelism
- Set up ConfigMaps and Secrets
- Create CI/CD pipeline
- Add Prometheus/Grafana monitoring
Next: Learn cloud provider integrations (AWS, Azure, GCP) →