LiveLoad

Elixir CILicense: MITHex version badge

LiveLoad Logo

A load testing framework for simulating real, distributed, live load on your application.

Why LiveLoad?

Load testing a Phoenix LiveView app is harder than it looks. The existing approaches all fall short in different ways:

What we actually need

  1. LiveView-aware metrics: not just "how long did the socket respond", but "how long did the click event take to process and patch the DOM".
  2. Real browsers: no handrolling protocol clients. Real Browser, running real JavaScript, connected to real WebSockets.
  3. More than one machine: because real browsers cost real resources.

LiveLoad is built on three foundations that compose naturally because they're all just BEAM primitives:

No custom RPC layer, no node-discovery protocol, no work-distribution scheduler. The BEAM does everything for us.

Installation

LiveLoad is available on Hex.

To install, add it to you dependencies in your project's mix.exs.

def deps do
  [
    {:live_load, ">= 0.0.1"}
  ]
end

Then install the Playwright driver and browser binaries:

mix live_load.install

This downloads the Playwright standalone driver (no npm install required) and installs the browser into the priv directory for the live_load application. The binaries are platform-specific, so for production or CI environments, you'll need to run this on the target architecture.

Docker / Release Builds

If you're running LiveLoad in a Docker container (which you probably are for distributed runs), you'll need to add Chromium's system dependencies to your Dockerfile. Add the following to your final Docker image layer:

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
      libglib2.0-0 \
      libnss3 \
      libnspr4 \
      libatk1.0-0 \
      libatk-bridge2.0-0 \
      libcups2 \
      libdbus-1-3 \
      libdrm2 \
      libxkbcommon0 \
      libxcomposite1 \
      libxdamage1 \
      libxext6 \
      libxfixes3 \
      libxrandr2 \
      libgbm1 \
      libpango-1.0-0 \
      libcairo2 \
      libasound2t64 \
      libatspi2.0-0 \
      libx11-6 \
      libxcb1 \
      libexpat1 \
  && rm -rf /var/lib/apt/lists/*

Quick Example

Define a scenario:

defmodule MyApp.LoadTest.BrowseScenario do
  use LiveLoad.Scenario

  @impl true
  def run(context, _user_id, _config) do
    context
    |> navigate("https://myapp.com/")
    |> wait_for_liveview()
    |> click("#load-more")
    |> wait_for_phx_loading_completion(:click, "#load-more")
  end
end

Run it:

results = LiveLoad.run(
  scenario: MyApp.LoadTest.BrowseScenario,
  users: 50,
  scenario_duration: to_timeout(minute: 5)
)

Generate a report:

html = LiveLoad.Reporter.HTML.render!(results)
File.write!("liveload_report.html", html)

Distributed Runs

For larger tests, LiveLoad can distribute users across a FLAME-provisioned cluster of nodes:

results = LiveLoad.run(
  otp_app: :my_app,
  users: 10_000,
  distributed?: true,
  flame_backend: FLAME.FlyBackend,
  cluster_opts: [
    flame_backend_opts: [app: :my_runner_app, cpus: 8, memory_mb: 16 * 1024],
    max_allowed_nodes: 100
  ]
)

LiveLoad handles the cluster formation, browser provisioning, and user distribution across nodes automatically. Each node gets its own browser instance, and users are distributed evenly across the cluster. When the test finishes, metrics from all nodes are merged into a single LiveLoad.Result.

Project Status

LiveLoad is in active early development. The architecture works and is tested against real applications, but there are rough edges that are being actively worked on.

Current limitations:

Documentation

Full documentation is available on HexDocs.

The Writing Your First Scenario guide is the best place to start. It covers everything from basic navigation to throttles, assigns, and the full scenario lifecycle.