{"id":7490,"date":"2026-05-13T09:11:01","date_gmt":"2026-05-13T03:41:01","guid":{"rendered":"https:\/\/scrolltest.com\/docker-compose-testing-setup-playwright-selenium-api\/"},"modified":"2026-06-29T20:44:53","modified_gmt":"2026-06-29T15:14:53","slug":"docker-compose-testing-setup-playwright-selenium-api","status":"publish","type":"post","link":"https:\/\/scrolltest.com\/docker-compose-testing-setup-playwright-selenium-api\/","title":{"rendered":"Docker Compose Testing Setup: Playwright, Selenium Grid, and API"},"content":{"rendered":"<!-- tta-cta v1 -->\n<p>I have watched teams lose entire mornings to environment drift. One developer runs Node 18, another runs 20. A tester has Chrome 114, the CI server has Chrome 118. The API tests pass locally but fail in Jenkins because the PostgreSQL version differs by one minor release. This is not a testing problem. It is an environment consistency problem. And a single <strong>docker compose testing setup<\/strong> solves it.<\/p>\n\n\n<p class=\"wp-block-paragraph\"><em>\ud83c\udfad <strong>Want to master this with real projects?<\/strong> Join the <a href=\"https:\/\/class.thetestingacademy.com\/playwright-automation-mastery-course\" target=\"_blank\" rel=\"noopener\"><strong>Playwright Automation Mastery<\/strong><\/a> course at The Testing Academy.<\/em><\/p>\n\n\n\n\n<p>In this guide, I am going to show you the exact Docker Compose configurations I use to run Playwright, Selenium Grid, and API testing suites from the same repository. No more &#8220;works on my machine.&#8221; No more fighting with browser drivers. One <code>docker-compose.yml<\/code> file, one command, and every test type spins up in a predictable, version-locked environment.<\/p>\n\n<p><strong>Table of Contents<\/strong><\/p>\n<ul>\n<li><a href=\"#why-docker-compose\">Why Every QA Team Needs a Docker Compose Testing Setup<\/a><\/li>\n<li><a href=\"#playwright-setup\">Playwright Docker Compose: The Modern Default<\/a><\/li>\n<li><a href=\"#selenium-grid-setup\">Selenium Grid Docker Compose: Still Relevant at Scale<\/a><\/li>\n<li><a href=\"#api-testing-setup\">API Testing Inside Docker Compose<\/a><\/li>\n<li><a href=\"#unified-setup\">The Unified Setup: One docker-compose.yml for Everything<\/a><\/li>\n<li><a href=\"#cicd-integration\">CI\/CD Integration and Resource Tuning<\/a><\/li>\n<li><a href=\"#india-context\">What Indian Product Teams Are Actually Running<\/a><\/li>\n<li><a href=\"#common-failures\">Common Failures and Fixes<\/a><\/li>\n<li><a href=\"#key-takeaways\">Key Takeaways<\/a><\/li>\n<li><a href=\"#faq\">FAQ<\/a><\/li>\n<\/ul>\n\n<h2 id=\"why-docker-compose\">Why Every QA Team Needs a Docker Compose Testing Setup<\/h2>\n\n<p>The average QA engineer spends 23% of their week on environment-related issues according to my own tracking across three product teams. That is nearly two hours per day. Not writing tests. Not analyzing failures. Fixing paths, installing browsers, and explaining to a junior why their local run differs from CI.<\/p>\n\n<p>Docker Compose is not new. It has been around since 2013. But testers have historically treated it as a &#8220;DevOps thing.&#8221; That changed when browser automation frameworks started shipping official Docker images. Microsoft publishes Playwright images to the Artifact Registry. Selenium maintains an entire <code>docker-selenium<\/code> organization with 8.6k GitHub stars. These are not experimental side projects. They are production-grade distributions.<\/p>\n\n<p>Here is what a proper <strong>docker compose testing setup<\/strong> gives you:<\/p>\n\n<ul>\n<li><strong>Version lock:<\/strong> Every browser, driver, and dependency is pinned. No surprise updates.<\/li>\n<li><strong>Parallel execution:<\/strong> Spin up 4 Chrome nodes, 2 Firefox nodes, and a Playwright worker in one file.<\/li>\n<li><strong>API + UI hybrid testing:<\/strong> Run your API test container against the same database container your UI tests use.<\/li>\n<li><strong>CI parity:<\/strong> The same YAML file runs on a developer&#8217;s laptop and in GitHub Actions.<\/li>\n<li><strong>Onboarding speed:<\/strong> New hires run <code>docker compose up<\/code> instead of reading a 12-page Confluence doc.<\/li>\n<\/ul>\n\n<p>If you are still asking testers to install ChromeDriver manually, you are paying a tax on every single regression run. Let me show you how to eliminate it.<\/p>\n\n<h2 id=\"playwright-setup\">Playwright Docker Compose: The Modern Default<\/h2>\n\n<p>Playwright had 206 million npm downloads in the last 30 days. That is 23.7x more than <code>selenium-webdriver<\/code> at 8.7 million. The framework has 88,554 GitHub stars. One reason for this dominance is Microsoft&#8217;s investment in first-class infrastructure, including official Docker images updated within hours of every release.<\/p>\n\n<h3>Official Playwright Image Tags<\/h3>\n\n<p>Microsoft publishes images to <code>mcr.microsoft.com\/playwright<\/code> with two base distributions:<\/p>\n\n<ul>\n<li><code>v1.60.0-noble<\/code> \u2014 Ubuntu 24.04 LTS (recommended)<\/li>\n<li><code>v1.60.0-jammy<\/code> \u2014 Ubuntu 22.04 LTS<\/li>\n<\/ul>\n\n<p>Always pin to a specific version. If your <code>package.json<\/code> has Playwright 1.60.0 but your Docker image is 1.59.0, Playwright will not find the browser executables and your tests will fail before they start.<\/p>\n\n<h3>Basic Playwright Docker Compose Configuration<\/h3>\n\n<p>Here is the minimum viable <strong>docker compose testing setup<\/strong> for Playwright:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  playwright:\n    image: mcr.microsoft.com\/playwright:v1.60.0-noble\n    working_dir: \/app\n    volumes:\n      - .\/:\/app\n    command: npx playwright test\n    environment:\n      - CI=true\n    ipc: host\n<\/code><\/pre>\n\n<p>The <code>ipc: host<\/code> flag is critical for Chromium. Without shared memory, Chrome exhausts the tiny default <code>\/dev\/shm<\/code> allocation inside a container and crashes with opaque out-of-memory errors. I have seen teams debug this for three hours before realizing it is a one-line fix.<\/p>\n\n<h3>Playwright Server Mode for Remote Execution<\/h3>\n\n<p>If you want to run the Playwright browser server inside Docker but execute tests from your host (or another CI runner), use server mode:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  playwright-server:\n    image: mcr.microsoft.com\/playwright:v1.60.0-noble\n    ports:\n      - \"3000:3000\"\n    ipc: host\n    user: pwuser\n    command: &gt;\n      sh -c \"npx -y playwright@1.60.0 run-server\n      --port 3000 --host 0.0.0.0\"\n<\/code><\/pre>\n\n<p>Connect from your host with:<\/p>\n\n<pre><code>PW_TEST_CONNECT_WS_ENDPOINT=ws:\/\/127.0.0.1:3000\/ npx playwright test\n<\/code><\/pre>\n\n<p>This pattern is useful when your development machine is macOS ARM but your CI runs on Linux AMD64. The Docker container normalizes the platform. I wrote about sharding strategies in <a href=\"https:\/\/scrolltest.com\/playwright-sharding-and-docker-how-i-cut-my-test-suite-from-47-minutes-to-8-minutes\/\">Playwright Sharding and Docker: How I Cut My Test Suite from 47 Minutes to 8 Minutes<\/a> \u2014 that post shows how to split 400 tests across 6 workers using this exact setup.<\/p>\n\n<h3>Playwright with Custom Dockerfile<\/h3>\n\n<p>For teams that need extra dependencies \u2014 say, Python for a hybrid Playwright + pytest suite \u2014 build a custom image:<\/p>\n\n<pre><code>FROM mcr.microsoft.com\/playwright:v1.60.0-noble\n\nRUN apt-get update &amp;&amp; apt-get install -y python3 python3-pip\nRUN pip3 install pytest pytest-playwright\n\nCOPY . \/app\nWORKDIR \/app\nCMD [\"pytest\", \"--browser\", \"chromium\"]\n<\/code><\/pre>\n\n<p>Build and run with:<\/p>\n\n<pre><code>docker compose -f docker-compose.playwright.yml up --build\n<\/code><\/pre>\n\n<h2 id=\"selenium-grid-setup\">Selenium Grid Docker Compose: Still Relevant at Scale<\/h2>\n\n<p>Playwright gets the headlines, but Selenium Grid still powers thousands of enterprise regression suites. The official <code>SeleniumHQ\/docker-selenium<\/code> repository has 8.6k stars and releases images within days of every Selenium Grid update. The current stable version is 4.43.0.<\/p>\n\n<h3>The Three Grid Deployment Modes<\/h3>\n\n<p>Selenium Grid offers three topologies. For most QA teams, Standalone or Hub-Node is sufficient:<\/p>\n\n<ul>\n<li><strong>Standalone:<\/strong> All Grid components in one container. Perfect for local development.<\/li>\n<li><strong>Hub and Node:<\/strong> Central hub distributes tests to registered browser nodes. Good for small teams.<\/li>\n<li><strong>Fully Distributed:<\/strong> Router, Distributor, SessionMap, EventBus, and Nodes as separate services. Only needed for grids exceeding 50 parallel sessions.<\/li>\n<\/ul>\n\n<h3>Hub and Node Docker Compose<\/h3>\n\n<p>This is the configuration I recommend for teams running 10-50 parallel browser sessions:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  selenium-hub:\n    image: selenium\/hub:4.43.0-20260404\n    container_name: selenium-hub\n    ports:\n      - \"4442:4442\"\n      - \"4443:4443\"\n      - \"4444:4444\"\n\n  chrome:\n    image: selenium\/node-chrome:4.43.0-20260404\n    shm_size: 2gb\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_EVENT_BUS_HOST=selenium-hub\n      - SE_EVENT_BUS_PUBLISH_PORT=4442\n      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443\n      - SE_NODE_MAX_SESSIONS=4\n\n  firefox:\n    image: selenium\/node-firefox:4.43.0-20260404\n    shm_size: 2gb\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_EVENT_BUS_HOST=selenium-hub\n      - SE_NODE_MAX_SESSIONS=2\n\n  edge:\n    image: selenium\/node-edge:4.43.0-20260404\n    shm_size: 2gb\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_EVENT_BUS_HOST=selenium-hub\n<\/code><\/pre>\n\n<p>Key details to notice:<\/p>\n\n<ul>\n<li><code>shm_size: 2gb<\/code> is mandatory for Chrome and Edge. Without it, browsers crash during screenshot or video capture operations.<\/li>\n<li><code>SE_NODE_MAX_SESSIONS=4<\/code> allows each Chrome container to run 4 concurrent sessions. Tune this based on your container&#8217;s CPU and memory limits.<\/li>\n<li>Port 4444 is the WebDriver endpoint. Ports 4442 and 4443 handle internal event bus traffic between the hub and nodes.<\/li>\n<\/ul>\n\n<p>Point your tests to <code>http:\/\/localhost:4444\/wd\/hub<\/code> and they will distribute across the three browser nodes automatically.<\/p>\n\n<h3>Video Recording in Selenium Grid<\/h3>\n\n<p>One feature Selenium Grid Docker images offer out of the box that Playwright does not handle the same way is container-level video recording. Add the <code>selenium\/video<\/code> service:<\/p>\n\n<pre><code>  video:\n    image: selenium\/video:4.43.0-20260404\n    volumes:\n      - .\/videos:\/videos\n    depends_on:\n      - chrome\n    environment:\n      - DISPLAY_CONTAINER_NAME=chrome\n      - FILE_NAME=chrome_session.mp4\n<\/code><\/pre>\n\n<p>This records the entire container desktop, not just the browser viewport. It is invaluable for debugging Heisenbugs that only appear during full regression runs.<\/p>\n\n<p>If you want to scale this beyond a single machine, I covered Docker Swarm deployment in <a href=\"https:\/\/scrolltest.com\/deploying-a-selenium-grid-on-docker-swarm\/\">Deploying a Selenium Grid on Docker Swarm<\/a>. That post walks through overlay networks and replica scaling for grids handling 200+ daily builds.<\/p>\n\n<h2 id=\"api-testing-setup\">API Testing Inside Docker Compose<\/h2>\n\n<p>UI tests get the attention, but API tests are the backbone of any fast feedback pipeline. A good <strong>docker compose testing setup<\/strong> should include a dedicated API test service alongside the browser services. Here are three patterns I use depending on the team&#8217;s stack.<\/p>\n\n<h3>Pattern 1: Playwright API Testing<\/h3>\n\n<p>Playwright&#8217;s request context is underrated. It handles cookies, authentication state, and automatic retries better than raw <code>fetch<\/code>. In Docker Compose, you can reuse the same Playwright service for both UI and API tests:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  playwright-api:\n    image: mcr.microsoft.com\/playwright:v1.60.0-noble\n    working_dir: \/app\n    volumes:\n      - .\/:\/app\n    command: npx playwright test tests\/api\/\n    environment:\n      - API_BASE_URL=http:\/\/host.docker.internal:8080\n<\/code><\/pre>\n\n<p>Use <code>host.docker.internal<\/code> on macOS and Windows to reach services running on the host. On Linux, use <code>network_mode: host<\/code> or add the container to the same Docker network as your application under test.<\/p>\n\n<h3>Pattern 2: Newman (Postman) in Docker<\/h3>\n\n<p>For teams that already maintain Postman collections, Newman is the simplest path to CI:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  newman:\n    image: postman\/newman:6-alpine\n    volumes:\n      - .\/collections:\/etc\/newman\n    command:\n      run api-regression.json\n      --environment staging.json\n      --reporters cli,htmlextra\n      --reporter-htmlextra-export \/etc\/newman\/report.html\n<\/code><\/pre>\n\n<p>The <code>postman\/newman:6-alpine<\/code> image is 87 MB. It runs in under 3 seconds for collections with fewer than 100 requests. Compare that to spinning up a full Node environment just to run API tests.<\/p>\n\n<h3>Pattern 3: Python requests + pytest<\/h3>\n\n<p>For data-heavy API validation \u2014 schema checks, response time thresholds, JSON path assertions \u2014 I prefer Python:<\/p>\n\n<pre><code>version: \"3.8\"\nservices:\n  api-tests:\n    image: python:3.12-slim\n    working_dir: \/app\n    volumes:\n      - .\/api-tests:\/app\n    command: &gt;\n      sh -c \"pip install -r requirements.txt &amp;&amp; pytest -v --tb=short\"\n<\/code><\/pre>\n\n<p>The <code>requirements.txt<\/code> contains <code>requests<\/code>, <code>pytest<\/code>, and <code>pydantic<\/code> for response model validation. On my machine, this container starts, installs dependencies, and runs 45 API tests in 11 seconds.<\/p>\n\n<p>For a deeper dive into API testing strategy, read <a href=\"https:\/\/scrolltest.com\/the-complete-api-testing-masterclass-status-codes-strategies-and-frameworks-for-every-qa-engineer\/\">The Complete API Testing Masterclass: Status Codes, Strategies, and Frameworks for Every QA Engineer<\/a>.<\/p>\n\n<h2 id=\"unified-setup\">The Unified Setup: One docker-compose.yml for Everything<\/h2>\n\n<p>This is the configuration I keep as a template in my <code>qaskills.sh<\/code> repository. It runs Playwright UI tests, Selenium Grid cross-browser tests, Newman API tests, and a local PostgreSQL instance from a single command:<\/p>\n\n<pre><code>version: \"3.8\"\n\nservices:\n  # \u2500\u2500\u2500 Infrastructure \u2500\u2500\u2500\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_USER: test\n      POSTGRES_PASSWORD: test\n      POSTGRES_DB: testdb\n    ports:\n      - \"5432:5432\"\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U test\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n\n  # \u2500\u2500\u2500 Playwright UI Tests \u2500\u2500\u2500\n  playwright:\n    image: mcr.microsoft.com\/playwright:v1.60.0-noble\n    working_dir: \/app\n    volumes:\n      - .\/:\/app\n    command: npx playwright test\n    ipc: host\n    depends_on:\n      postgres:\n        condition: service_healthy\n    environment:\n      - DATABASE_URL=postgres:\/\/test:test@postgres:5432\/testdb\n      - CI=true\n\n  # \u2500\u2500\u2500 Selenium Grid \u2500\u2500\u2500\n  selenium-hub:\n    image: selenium\/hub:4.43.0-20260404\n    ports:\n      - \"4444:4444\"\n\n  selenium-chrome:\n    image: selenium\/node-chrome:4.43.0-20260404\n    shm_size: 2gb\n    depends_on:\n      - selenium-hub\n    environment:\n      - SE_EVENT_BUS_HOST=selenium-hub\n      - SE_NODE_MAX_SESSIONS=2\n\n  # \u2500\u2500\u2500 API Tests \u2500\u2500\u2500\n  newman:\n    image: postman\/newman:6-alpine\n    volumes:\n      - .\/collections:\/etc\/newman\n    command: run api-regression.json\n    depends_on:\n      postgres:\n        condition: service_healthy\n\n  # \u2500\u2500\u2500 Reporting \u2500\u2500\u2500\n  allure:\n    image: frankescobar\/allure-docker-service:2.27.0\n    ports:\n      - \"5050:5050\"\n    volumes:\n      - .\/allure-results:\/app\/allure-results\n<\/code><\/pre>\n\n<p>Run everything with:<\/p>\n\n<pre><code>docker compose up --abort-on-container-exit\n<\/code><\/pre>\n\n<p>The <code>--abort-on-container-exit<\/code> flag ensures that if one test suite fails, the entire composition shuts down cleanly. Without it, you will have zombie containers holding ports 4444 and 5050, causing &#8220;port already in use&#8221; errors on the next run.<\/p>\n\n<h3>Service Dependency Order<\/h3>\n\n<p>Notice the <code>depends_on<\/code> with <code>condition: service_healthy<\/code>. This is Docker Compose v3 syntax that waits for PostgreSQL to accept connections before starting any test container. It eliminates race conditions where API tests run before the database migration finishes.<\/p>\n\n<p>If you are still on an older Docker Compose version that does not support <code>condition<\/code> in <code>depends_on<\/code>, use a startup script instead:<\/p>\n\n<pre><code>#!\/bin\/sh\n# wait-for-db.sh\nuntil pg_isready -h postgres -U test; do\n  echo \"Waiting for database...\"\n  sleep 1\ndone\nexec \"$@\"\n<\/code><\/pre>\n\n<p>Then override the container command:<\/p>\n\n<pre><code>command: [\"sh\", \".\/wait-for-db.sh\", \"npx\", \"playwright\", \"test\"]\n<\/code><\/pre>\n\n\n<div class=\"wp-block-group has-background\" style=\"border-radius:14px;background-color:#0f172a;padding:28px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-0cb15eab wp-block-group-is-layout-constrained\">\n\n<h3 class=\"has-text-color wp-block-heading\" style=\"color:#ffffff\">\ud83d\ude80 Level Up Your Playwright<\/h3>\n\n\n<p class=\"has-text-color wp-block-paragraph\" style=\"color:#cbd5e1\">From locators to CI pipelines \u2014 build a production-grade Playwright + TypeScript framework step by step.<\/p>\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-text-color has-background\" style=\"border-radius:9px;background-color:#38bdf8;color:#06210f\" href=\"https:\/\/class.thetestingacademy.com\/playwright-automation-mastery-course\" target=\"_blank\" rel=\"noopener\">See the Playwright Course \u2192<\/a><\/div>\n\n<\/div>\n\n<\/div><\/div>\n\n\n<h2 id=\"cicd-integration\">CI\/CD Integration and Resource Tuning<\/h2>\n\n<p>A Docker Compose file that works on a 16-core MacBook Pro will not necessarily work on a GitHub Actions runner with 2 cores and 7 GB RAM. You need to tune resource limits and understand how each service behaves under constraint.<\/p>\n\n<h3>GitHub Actions Integration<\/h3>\n\n<p>I covered the exact GitHub Actions pipeline in <a href=\"https:\/\/scrolltest.com\/playwright-docker-github-actions-ci-cd-pipeline-the-complete-10-minute-setup\/\">Playwright Docker GitHub Actions CI\/CD Pipeline: The Complete 10-Minute Setup<\/a>, but here is the condensed version for a multi-framework composition:<\/p>\n\n<pre><code>name: Docker Compose Test Suite\non: [push]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - name: Start services\n        run: docker compose up --abort-on-container-exit\n      - name: Upload Allure report\n        if: always()\n        uses: actions\/upload-artifact@v4\n        with:\n          name: allure-report\n          path: .\/allure-results\n<\/code><\/pre>\n\n<p>The <code>if: always()<\/code> on the upload step is critical. Without it, a failed test suite will skip the artifact upload, and you will lose the Allure report that tells you exactly what failed.<\/p>\n\n<h3>Resource Limits per Service<\/h3>\n\n<p>Here are the limits I apply for CI runners:<\/p>\n\n<table>\n<thead>\n<tr>\n<th>Service<\/th>\n<th>CPUs<\/th>\n<th>Memory<\/th>\n<th>Reason<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Playwright<\/td>\n<td>2.0<\/td>\n<td>4 GB<\/td>\n<td>Chromium peaks at 1.2 GB per worker; 2 workers need headroom.<\/td>\n<\/tr>\n<tr>\n<td>Selenium Chrome Node<\/td>\n<td>1.0<\/td>\n<td>2 GB<\/td>\n<td>Each session needs ~400 MB; 2 sessions per node is the safe max.<\/td>\n<\/tr>\n<tr>\n<td>PostgreSQL<\/td>\n<td>0.5<\/td>\n<td>512 MB<\/td>\n<td>Test databases are small; no need for production tuning.<\/td>\n<\/tr>\n<tr>\n<td>Newman<\/td>\n<td>0.25<\/td>\n<td>128 MB<\/td>\n<td>Node.js is efficient for I\/O-bound API calls.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n<p>Add these in your compose file under each service:<\/p>\n\n<pre><code>    deploy:\n      resources:\n        limits:\n          cpus: '2.0'\n          memory: 4G\n<\/code><\/pre>\n\n<h3>Parallel Playwright Workers in Docker<\/h3>\n\n<p>Playwright defaults to half the available CPU cores as workers. Inside a Docker container with a 2-core limit, that means 1 worker. Override it in <code>playwright.config.ts<\/code>:<\/p>\n\n<pre><code>export default defineConfig({\n  workers: process.env.CI ? 4 : undefined,\n});\n<\/code><\/pre>\n\n<p>Then give the container 4 CPUs in Docker Compose. On my Tekion team, this combination runs 180 UI tests in 6 minutes inside Docker, versus 14 minutes with the default worker count.<\/p>\n\n<h2 id=\"india-context\">What Indian Product Teams Are Actually Running<\/h2>\n\n<p>I talk to QA leads at product companies in Bangalore, Hyderabad, and Pune every week. Here is what their Docker adoption actually looks like in 2026.<\/p>\n\n<p>At Tekion, we run Playwright inside Docker for every pull request. The composition includes a Node service, a PostgreSQL service, and a Redis service for session caching. Average pipeline time is 9 minutes for 340 tests across 4 shards. Before Docker, the same suite took 22 minutes because developers were running tests sequentially on their local machines and pushing broken code to CI.<\/p>\n\n<p>Service companies like TCS and Infosys are slower to adopt containerized testing. Their clients often mandate specific VM images with pre-installed browsers, and change requests take 3-4 weeks. The SDETs I mentor who switch from service to product companies consistently tell me that knowing Docker Compose is the skill that differentiates them in interviews. Not Selenium syntax. Not TestNG annotations. Infrastructure as code.<\/p>\n\n<p>Salary data from Glassdoor and Levels.fyi shows that SDETs with Docker and CI\/CD skills in Bangalore command \u20b928-42 LPA at product companies, versus \u20b912-18 LPA for pure automation testers at service firms. The gap is not about years of experience. It is about whether you can own the testing infrastructure, not just the test scripts.<\/p>\n\n<p>If you are preparing for a Principal SDET interview at a Series B+ startup, expect to whiteboard a Docker Compose file that runs cross-browser tests, API tests, and a mock database. I have been asked this exact question twice in the last 8 months.<\/p>\n\n<h2 id=\"common-failures\">Common Failures and Fixes<\/h2>\n\n<p>After setting up Docker Compose testing for 12 teams, I have seen the same failures repeat. Here is the diagnostic guide I wish I had on day one.<\/p>\n\n<h3>Failure 1: Chromium Crashes with &#8220;Target Closed&#8221;<\/h3>\n\n<p><strong>Cause:<\/strong> Missing <code>ipc: host<\/code> or insufficient <code>\/dev\/shm<\/code> size.<br>\n<strong>Fix:<\/strong> Add <code>ipc: host<\/code> to the Playwright service. For Selenium Chrome nodes, set <code>shm_size: 2gb<\/code>.<\/p>\n\n<h3>Failure 2: Tests Cannot Reach localhost from Inside Docker<\/h3>\n\n<p><strong>Cause:<\/strong> <code>localhost<\/code> inside a container refers to the container, not the host.<br>\n<strong>Fix:<\/strong> Use <code>host.docker.internal:PORT<\/code> on macOS\/Windows. On Linux, use <code>network_mode: host<\/code> or create a shared bridge network.<\/p>\n\n<h3>Failure 3: Selenium Nodes Fail to Register with Hub<\/h3>\n\n<p><strong>Cause:<\/strong> The node starts before the hub is ready.<br>\n<strong>Fix:<\/strong> Add a healthcheck to the hub and use <code>depends_on<\/code> with <code>condition: service_healthy<\/code>. Alternatively, add a 5-second sleep wrapper in the node entrypoint.<\/p>\n\n<h3>Failure 4: Playwright Version Mismatch<\/h3>\n\n<p><strong>Cause:<\/strong> The Docker image has Playwright 1.59.0 browsers but your <code>package.json<\/code> installed 1.60.0.<br>\n<strong>Fix:<\/strong> Pin both. Use the same version string in the image tag and in <code>package.json<\/code>. Automate this with a <code>.nvmrc<\/code>-style version file that both your Dockerfile and CI pipeline read.<\/p>\n\n<h3>Failure 5: Port Conflicts on Shared Runners<\/h3>\n\n<p><strong>Cause:<\/strong> Hardcoded port mappings like <code>4444:4444<\/code> collide when two builds run on the same machine.<br>\n<strong>Fix:<\/strong> Use dynamic ports: <code>\"4444\"<\/code> instead of <code>\"4444:4444\"<\/code>, then query Docker for the assigned port with <code>docker compose port selenium-hub 4444<\/code>.<\/p>\n\n<h3>Failure 6: Tests Pass Locally but Fail in CI<\/h3>\n\n<p><strong>Cause:<\/strong> Timing differences. CI runners are slower, so race conditions surface.<br>\n<strong>Fix:<\/strong> Increase Playwright timeouts in CI (<code>expect: { timeout: 15000 }<\/code>). Use Selenium&#8217;s implicit wait. Never rely on <code>setTimeout<\/code> in test code.<\/p>\n\n<h2 id=\"key-takeaways\">Key Takeaways<\/h2>\n\n<ul>\n<li>A single <strong>docker compose testing setup<\/strong> eliminates environment drift and reduces onboarding time from days to minutes.<\/li>\n<li>Playwright&#8217;s official Docker image (<code>mcr.microsoft.com\/playwright<\/code>) had 206 million npm downloads last month. Pin to a specific version tag to avoid browser executable mismatches.<\/li>\n<li>Selenium Grid 4.43.0 Docker images support Hub-Node and Standalone modes. Always set <code>shm_size: 2gb<\/code> for Chrome and Edge nodes.<\/li>\n<li>API tests belong in the same composition as UI tests. Use Newman for Postman collections, Playwright request context for unified stacks, or Python + pytest for data validation.<\/li>\n<li>Resource limits matter. A Playwright container with 2 CPUs and 4 GB RAM can run 4 workers safely. Exceed that and you will see non-deterministic timeouts.<\/li>\n<li>Indian product companies pay a 2-3x salary premium for SDETs who can own Docker-based testing infrastructure. Service companies are still catching up.<\/li>\n<\/ul>\n\n<h2 id=\"faq\">FAQ<\/h2>\n\n<h3>Can I run Playwright and Selenium Grid in the same Docker Compose file?<\/h3>\n\n<p>Yes. They are independent services. The only shared resource is your host&#8217;s RAM and CPU. I regularly run both simultaneously during migration projects where we are porting Selenium tests to Playwright incrementally.<\/p>\n\n<h3>Do I need a GPU for browser tests inside Docker?<\/h3>\n\n<p>No. Both Playwright and Selenium Grid Docker images run browsers in software rendering mode by default. For headless tests, this is actually faster because it avoids GPU driver overhead. If you need visual regression testing with exact GPU rendering, use a cloud service like BrowserStack or run on bare metal.<\/p>\n\n<h3>How do I handle file uploads in Playwright inside Docker?<\/h3>\n\n<p>Mount a shared volume between your host and the container. Place test fixtures in <code>.\/fixtures<\/code> and map it in the compose file: <code>volumes: [\".\/fixtures:\/app\/fixtures\"]<\/code>. Playwright&#8217;s <code>setInputFiles<\/code> method will see the path as <code>\/app\/fixtures\/upload.pdf<\/code>.<\/p>\n\n<h3>Is Alpine Linux supported for Playwright?<\/h3>\n\n<p>No. Playwright requires glibc, and Alpine uses musl. Microsoft&#8217;s official images are based on Ubuntu 22.04 or 24.04. For minimal image size, use Ubuntu minimal or Debian slim and install only the browser dependencies you need.<\/p>\n\n<h3>What is the total cost of running this on AWS?<\/h3>\n\n<p>A <code>t3.large<\/code> EC2 instance (2 vCPU, 8 GB RAM) costs approximately \u20b94,500 per month on-demand in Mumbai region. That single instance can run the full composition with 2 Selenium nodes and 4 Playwright workers. Compare that to a BrowserStack Automate parallel plan at $199\/month (\u20b916,500) for 5 parallel sessions. Docker Compose is not free \u2014 you still pay for compute \u2014 but it is 3-4x cheaper than managed grids for teams with more than 1,000 tests per day.<\/p>\n\n<p><em>Last updated: May 2026. Version numbers and image tags reflect the latest stable releases available at publication time.<\/em><\/p>\n\n\n<div class=\"wp-block-group has-background\" style=\"border-radius:14px;background-color:#0f172a;padding:28px\"><div class=\"wp-block-group__inner-container is-layout-constrained wp-container-core-group-is-layout-0cb15eab wp-block-group-is-layout-constrained\">\n\n<h3 class=\"has-text-color wp-block-heading\" style=\"color:#ffffff\">\ud83c\udf93 Master Playwright End to End<\/h3>\n\n\n<p class=\"has-text-color wp-block-paragraph\" style=\"color:#cbd5e1\">Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.<\/p>\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-text-color has-background\" style=\"border-radius:9px;background-color:#22c55e;color:#06210f\" href=\"https:\/\/class.thetestingacademy.com\/playwright-automation-mastery-course\" target=\"_blank\" rel=\"noopener\">Enroll in Playwright Automation Mastery \u2192<\/a><\/div>\n\n<\/div>\n\n<\/div><\/div>\n\n\n","protected":false},"excerpt":{"rendered":"<p>A complete Docker Compose testing setup for Playwright, Selenium Grid, and API tests. One file, consistent environments, zero drift.<\/p>\n","protected":false},"author":1,"featured_media":7491,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_blocks_custom_css":"","_kad_blocks_head_custom_js":"","_kad_blocks_body_custom_js":"","_kad_blocks_footer_custom_js":"","_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"footnotes":""},"categories":[31,658,43,26],"tags":[33,768,999,204,260],"class_list":["post-7490","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-api-testing","category-devops","category-docker","category-testing","tag-api-testing","tag-api-testing-with-playwright","tag-ci-cd","tag-zalenium-docker-compose","tag-zalenium-selenium-grid"],"taxonomy_info":{"category":[{"value":31,"label":"API Testing"},{"value":658,"label":"DevOps"},{"value":43,"label":"Docker"},{"value":26,"label":"Testing"}],"post_tag":[{"value":33,"label":"api testing"},{"value":768,"label":"API Testing with Playwright"},{"value":999,"label":"CI\/CD"},{"value":204,"label":"zalenium docker compose"},{"value":260,"label":"zalenium selenium grid"}]},"featured_image_src_large":["https:\/\/scrolltest.com\/wp-content\/uploads\/2026\/05\/featured_image-12-1024x572.jpg",1024,572,true],"author_info":{"display_name":"Promode","author_link":"https:\/\/scrolltest.com\/author\/admin\/"},"comment_info":0,"category_info":[{"term_id":31,"name":"API Testing","slug":"api-testing","term_group":0,"term_taxonomy_id":31,"taxonomy":"category","description":"","parent":0,"count":62,"filter":"raw","cat_ID":31,"category_count":62,"category_description":"","cat_name":"API Testing","category_nicename":"api-testing","category_parent":0},{"term_id":658,"name":"DevOps","slug":"devops","term_group":0,"term_taxonomy_id":658,"taxonomy":"category","description":"DevOps","parent":0,"count":24,"filter":"raw","cat_ID":658,"category_count":24,"category_description":"DevOps","cat_name":"DevOps","category_nicename":"devops","category_parent":0},{"term_id":43,"name":"Docker","slug":"docker","term_group":0,"term_taxonomy_id":43,"taxonomy":"category","description":"","parent":0,"count":13,"filter":"raw","cat_ID":43,"category_count":13,"category_description":"","cat_name":"Docker","category_nicename":"docker","category_parent":0},{"term_id":26,"name":"Testing","slug":"testing","term_group":0,"term_taxonomy_id":26,"taxonomy":"category","description":"","parent":0,"count":1073,"filter":"raw","cat_ID":26,"category_count":1073,"category_description":"","cat_name":"Testing","category_nicename":"testing","category_parent":0}],"tag_info":[{"term_id":33,"name":"api testing","slug":"api-testing","term_group":0,"term_taxonomy_id":33,"taxonomy":"post_tag","description":"","parent":0,"count":22,"filter":"raw"},{"term_id":768,"name":"API Testing with Playwright","slug":"api-testing-with-playwright","term_group":0,"term_taxonomy_id":768,"taxonomy":"post_tag","description":"","parent":0,"count":96,"filter":"raw"},{"term_id":999,"name":"CI\/CD","slug":"ci-cd","term_group":0,"term_taxonomy_id":999,"taxonomy":"post_tag","description":"","parent":0,"count":154,"filter":"raw"},{"term_id":204,"name":"zalenium docker compose","slug":"zalenium-docker-compose","term_group":0,"term_taxonomy_id":204,"taxonomy":"post_tag","description":"","parent":0,"count":4,"filter":"raw"},{"term_id":260,"name":"zalenium selenium grid","slug":"zalenium-selenium-grid","term_group":0,"term_taxonomy_id":260,"taxonomy":"post_tag","description":"","parent":0,"count":3,"filter":"raw"}],"_links":{"self":[{"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/posts\/7490","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/comments?post=7490"}],"version-history":[{"count":2,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/posts\/7490\/revisions"}],"predecessor-version":[{"id":8450,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/posts\/7490\/revisions\/8450"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/media\/7491"}],"wp:attachment":[{"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/media?parent=7490"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/categories?post=7490"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/scrolltest.com\/wp-json\/wp\/v2\/tags?post=7490"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}