Browser tests that pass on a laptop can fail in continuous integration when the runner has a different browser, driver, viewport, or dependency set. Running Selenium tests in GitHub Actions turns a headless browser smoke test into a repository check that can block broken UI changes before merge.

A GitHub-hosted ubuntu-24.04 runner image includes Google Chrome and ChromeDriver, and actions/setup-python@v6 prepares the selected Python runtime before pytest runs. Printing the browser versions in the job log makes later failures easier to compare when the hosted runner image changes.

Keep the first CI test small and pointed at a stable staging URL. Authentication prompts, one-time passcodes, live payment flows, and fragile third-party pages make poor required checks because they can fail for reasons outside the code under review.

Steps to run Selenium tests in GitHub Actions:

  1. Add the Selenium test dependencies.
    selenium>=4.34,<5
    pytest>=8,<9
  2. Add a headless Chrome smoke test.
    tests/test_homepage.py
    import os
     
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.common.by import By
     
     
    def test_homepage_loads():
        app_url = os.environ["APP_URL"]
        expected_title = os.environ["EXPECTED_TITLE"]
     
        options = webdriver.ChromeOptions()
        options.add_argument("--headless=new")
        options.add_argument("--window-size=1280,720")
        options.add_argument("--disable-dev-shm-usage")
        if os.environ.get("CHROME_NO_SANDBOX") == "1":
            options.add_argument("--no-sandbox")
     
        chrome_binary = os.environ.get("CHROME_BINARY")
        if chrome_binary:
            options.binary_location = chrome_binary
     
        service_path = os.environ.get("CHROMEDRIVER")
        service = Service(service_path) if service_path else None
     
        driver = webdriver.Chrome(service=service, options=options)
        try:
            driver.get(app_url)
            assert expected_title in driver.title
            assert driver.find_element(By.TAG_NAME, "body").is_displayed()
        finally:
            driver.quit()

    Set CHROME_BINARY or CHROMEDRIVER only for self-hosted runners or containers that keep the browser outside the normal PATH. Set CHROME_NO_SANDBOX=1 only when a containerized runner cannot start Chrome with its sandbox.

  3. Create the GitHub Actions workflow.
    .github/workflows/selenium.yml
    name: Selenium CI
    
    on:
      push:
        branches: [main]
      pull_request:
      workflow_dispatch:
    
    jobs:
      selenium:
        runs-on: ubuntu-24.04
        permissions:
          contents: read
        timeout-minutes: 10
    
        steps:
          - uses: actions/checkout@v6
    
          - uses: actions/setup-python@v6
            with:
              python-version: "3.13"
              cache: "pip"
              cache-dependency-path: requirements-dev.txt
    
          - name: Install test dependencies
            run: python -m pip install -r requirements-dev.txt
    
          - name: Show browser versions
            run: |
              google-chrome --version
              chromedriver --version
    
          - name: Run Selenium tests
            env:
              APP_URL: https://example.com/
              EXPECTED_TITLE: Example Domain
            run: pytest -q

    Replace APP_URL and EXPECTED_TITLE with a stable staging page and a title fragment that belongs to the application. Use repository variables or secrets for private endpoints instead of hard-coding credentials in the workflow.

  4. Commit the test and workflow files.
    $ git add requirements-dev.txt tests/test_homepage.py .github/workflows/selenium.yml
    $ git commit -m "Run Selenium tests in GitHub Actions"
    [feature/selenium-ci 6f4a7d2] Run Selenium tests in GitHub Actions
     3 files changed, 61 insertions(+)
     create mode 100644 .github/workflows/selenium.yml
     create mode 100644 requirements-dev.txt
     create mode 100644 tests/test_homepage.py
  5. Push the branch so the push trigger starts the workflow.
    $ git push -u origin feature/selenium-ci
    Enumerating objects: 9, done.
    Counting objects: 100% (9/9), done.
    Writing objects: 100% (6/6), 1.42 KiB | 1.42 MiB/s, done.
    branch 'feature/selenium-ci' set up to track 'origin/feature/selenium-ci'.
  6. Check the newest Selenium CI run.
    $ gh run list --workflow selenium.yml --branch feature/selenium-ci --limit 1 --json databaseId,status,conclusion,event
    [{"conclusion":"success","databaseId":9812345671,"event":"push","status":"completed"}]

    The GitHub CLI command requires an authenticated session that can read Actions runs for the repository.

  7. Confirm that the run log shows the browser versions and the pytest pass.
    $ gh run view 9812345671 --log
    ##### snipped #####
    selenium  Show browser versions  Google Chrome 149.0.7827.53
    selenium  Show browser versions  ChromeDriver 149.0.7827.54
    selenium  Run Selenium tests  .                                                                        [100%]
    selenium  Run Selenium tests  1 passed in 3.21s

    If the conclusion is failure, run gh run view 9812345671 with --log-failed and fix the first failing setup or test step before making the workflow required.