tutorial

How to Monitor Hound with Vigilmon

Keep your Hound end-to-end browser tests healthy in CI — detect WebDriver failures, browser session timeouts, and integration test regressions before they silently skip coverage.

How to Monitor Hound with Vigilmon

Hound is an end-to-end browser automation library for Elixir that drives real browsers through WebDriver. Teams use it to write ExUnit integration tests that click buttons, fill forms, and verify page content in an actual Chrome or Firefox instance — the same way a user would. It integrates cleanly with Phoenix applications through Hound.Helpers and supports parallel test execution via async: true.

When Hound tests run, they provide the highest-fidelity coverage of your application: real HTTP requests, real JavaScript execution, real DOM rendering. When Hound breaks — a WebDriver session can't start, Chromedriver is the wrong version, or tests hang because an element selector changed — that coverage silently disappears from CI. Vigilmon heartbeats ensure your browser test suite runs and passes on every deploy.


Why Monitor Hound?

Hound failures are quiet and coverage-destroying:

  • WebDriver session startup failure — Chromedriver is missing or the wrong version for the installed Chrome; all Hound tests skip or error with connection refused, but CI may still pass if the test failure is not configured to block
  • Browser process timeout — a hanging JavaScript call causes a Hound test to block indefinitely; CI eventually times out but the failure message is generic
  • Selector drift — a frontend refactor changes a CSS class or data attribute that Hound selects; tests fail with element not found but only on the changed pages, making the regression hard to spot
  • Parallel session exhaustion — running too many async: true Hound tests exceeds the WebDriver session limit; some tests start in a broken session state
  • Missing heartbeat — the Hound test suite passes locally but is silently excluded from CI (wrong path glob, misconfigured matrix); production never runs browser tests without anyone noticing

Key Metrics to Track

| Metric | What it tells you | |--------|-------------------| | Hound suite exit code | Whether the browser test suite passed or failed | | CI heartbeat freshness | Whether browser tests ran recently (detects silent exclusions) | | WebDriver startup time | Whether session creation is slower than a baseline (signals Chromedriver issues) | | Test duration trend | Growing test run time signals page performance regressions under test | | Failed test names | Which specific pages or flows are breaking in the browser |


Step 1: Add Hound to Your Project

# mix.exs
defp deps do
  [
    {:hound, "~> 1.1", only: :test},
    # rest of deps
  ]
end

Configure Hound in config/test.exs:

# config/test.exs
config :hound, driver: "chrome_driver", browser: "chrome_headless"

For headless Chrome in CI:

config :hound,
  driver: "chrome_driver",
  browser: "chrome",
  browser_capabilities: %{
    chromeOptions: %{
      args: ["--headless", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage"]
    }
  }

Start Hound in test/test_helper.exs:

# test/test_helper.exs
Application.ensure_all_started(:hound)
ExUnit.start()

Step 2: Write a Smoke-Test Integration Suite

# test/integration/homepage_test.exs
defmodule MyAppWeb.HomepageIntegrationTest do
  use ExUnit.Case
  use Hound.Helpers

  hound_session()

  @moduletag :integration

  test "homepage loads and displays the product name" do
    navigate_to("http://localhost:4002/")

    assert page_title() =~ "MyApp"
    assert find_element(:css, "h1") |> inner_html() =~ "Welcome"
  end

  test "login form is present and accepts input" do
    navigate_to("http://localhost:4002/users/log_in")

    email_field = find_element(:id, "user_email")
    password_field = find_element(:id, "user_password")

    fill_field(email_field, "test@example.com")
    fill_field(password_field, "password123")

    assert attribute_value(email_field, "value") == "test@example.com"
  end

  test "navigation links are present" do
    navigate_to("http://localhost:4002/")

    links = find_all_elements(:css, "nav a")
    assert length(links) >= 2, "Expected at least 2 navigation links"
  end
end

Run it with ChromeDriver running:

chromedriver --port=9515 &
MIX_ENV=test mix phx.server &
MIX_ENV=test mix test test/integration/ --include integration

Step 3: Write a WebDriver Health Check

Before running Hound tests, verify that ChromeDriver is reachable and can start a session:

# lib/mix/tasks/check_webdriver.ex
defmodule Mix.Tasks.CheckWebdriver do
  use Mix.Task
  require Logger

  @shortdoc "Verify ChromeDriver is running and can start a session"

  def run(_args) do
    port = System.get_env("CHROMEDRIVER_PORT", "9515")
    url = "http://localhost:#{port}/status"

    Logger.info("Checking ChromeDriver at #{url}...")

    case :httpc.request(:get, {String.to_charlist(url), []}, [{:timeout, 5_000}], []) do
      {:ok, {{_, 200, _}, _, body}} ->
        body_str = to_string(body)

        if String.contains?(body_str, ~s("ready":true)) do
          Logger.info("ChromeDriver is ready")
        else
          Logger.error("ChromeDriver returned status but not ready: #{body_str}")
          System.halt(1)
        end

      {:ok, {{_, status, _}, _, _}} ->
        Logger.error("ChromeDriver returned unexpected status: #{status}")
        System.halt(1)

      {:error, reason} ->
        Logger.error("ChromeDriver not reachable: #{inspect(reason)}")
        Logger.error("Start it with: chromedriver --port=#{port}")
        System.halt(1)
    end
  end
end

Step 4: Run Hound Tests in CI with a Vigilmon Heartbeat

# .github/workflows/integration-tests.yml
name: Hound Integration Tests

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 */6 * * *'    # every 6 hours

jobs:
  hound:
    name: Browser Integration Tests
    runs-on: ubuntu-latest
    env:
      MIX_ENV: test
      VIGILMON_HOUND_HEARTBEAT_URL: ${{ secrets.VIGILMON_HOUND_HEARTBEAT_URL }}

    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v4

      - uses: erlef/setup-beam@v1
        with:
          elixir-version: '1.17'
          otp-version: '27'

      - name: Cache deps
        uses: actions/cache@v3
        with:
          path: deps
          key: ${{ runner.os }}-deps-${{ hashFiles('mix.lock') }}

      - run: mix deps.get

      - name: Setup database
        run: mix ecto.create && mix ecto.migrate

      - name: Install ChromeDriver
        uses: nanasess/setup-chromedriver@v2

      - name: Start ChromeDriver
        run: chromedriver --port=9515 &

      - name: Verify ChromeDriver
        run: MIX_ENV=test mix check_webdriver

      - name: Start Phoenix server
        run: MIX_ENV=test mix phx.server &
        env:
          PORT: 4002

      - name: Wait for server
        run: |
          for i in $(seq 1 15); do
            curl -sf http://localhost:4002/health && break
            sleep 2
          done

      - name: Run Hound integration tests
        run: mix test test/integration/ --include integration --color

      - name: Ping Vigilmon heartbeat
        if: success()
        run: curl -fsS "$VIGILMON_HOUND_HEARTBEAT_URL"

The heartbeat only fires on success() — if WebDriver fails to start, if a test hangs, or if a page element is missing, no heartbeat fires and Vigilmon alerts you.


Step 5: Create a Heartbeat Monitor in Vigilmon

  1. Sign in at vigilmon.online
  2. Click New Monitor → Heartbeat
  3. Name: Hound Integration Tests — main branch
  4. Expected interval: 7 hours (matches the 6-hour cron with 1 hour grace)
  5. Copy the URL → store as VIGILMON_HOUND_HEARTBEAT_URL in CI secrets

Step 6: Alerting

In Vigilmon, go to Notifications → New Channel → Slack:

Heartbeat missed:

🔴 MISSED: Hound Integration Tests — main branch
Last successful browser test run: 9 hours ago
Action: Check CI — ChromeDriver session failure or page element selector drift

Configure a 1-hour grace period to absorb occasional slow CI runners. A 7-hour expected interval on a 6-hour schedule means a single missed run still pages you within the hour.

For more granular alerts, set up separate heartbeat monitors per test suite area:

| Monitor | Expected interval | Covers | |---------|------------------|--------| | Hound — Auth Flow | 7 hours | Login, logout, session tests | | Hound — Checkout Flow | 7 hours | Cart, payment, order confirmation | | Hound — Admin Panel | 25 hours | Daily admin smoke test |

Each fires a separate alert so you know exactly which flow regressed without digging through CI logs first.


What You Built

| What | How | |------|-----| | Hound integration tests | ExUnit + Hound tests for homepage, forms, navigation | | WebDriver health check | Mix task verifies ChromeDriver readiness before test run | | CI workflow | GitHub Actions with Postgres, ChromeDriver, Phoenix server | | 6-hour schedule | Catches browser regressions between deploys, not just on push | | Vigilmon heartbeat | Alerts when browser test suite misses its schedule or fails | | Slack alerting | Vigilmon Slack notification channel |

Hound provides the most realistic test coverage available in Elixir. Vigilmon ensures that coverage actually runs and stays green.


Next Steps

  • Add per-flow heartbeat monitors so you know which specific user journey broke, not just that something failed
  • Use Hound's take_screenshot/1 on test failure and upload screenshots as CI artifacts for faster debugging
  • Track test duration in CI to detect pages that are getting slower under browser automation
  • Add a Vigilmon HTTP monitor for your staging environment alongside the CI heartbeat to catch runtime issues that tests don't exercise

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →