Playwriter's transport layer abstracts the communication between your Elixir application and the Playwright browser automation engine. This design enables local, Windows, and remote browser control through a unified API.

Transport Abstraction

All transports implement Playwriter.Transport.Behaviour, ensuring consistent behavior regardless of how you connect to Playwright:

@callback start_link(keyword()) :: {:ok, pid()} | {:error, term()}
@callback launch_browser(transport(), browser_type(), keyword()) :: {:ok, guid()}
@callback new_context(transport(), guid(), keyword()) :: {:ok, guid()}
@callback new_page(transport(), guid()) :: {:ok, map()}
@callback goto(transport(), guid(), String.t(), keyword()) :: result()
@callback content(transport(), guid()) :: {:ok, String.t()}
@callback click(transport(), guid(), String.t(), keyword()) :: result()
@callback fill(transport(), guid(), String.t(), String.t(), keyword()) :: result()
@callback screenshot(transport(), guid(), keyword()) :: {:ok, binary()}
@callback close_browser(transport(), guid()) :: :ok

Local Transport

The local transport (Playwriter.Transport.Local) wraps playwright_ex for direct browser control on the same machine.

How It Works

Your Application
      
      
Local Transport (GenServer)
      
      
PlaywrightEx Library
      
      
Erlang Port
      
      
Node.js Playwright Driver
      
      
Browser (Chromium/Firefox/WebKit)

Usage

Local transport is the default when no mode option is specified:

# These are equivalent
Playwriter.fetch_html("https://example.com")
Playwriter.fetch_html("https://example.com", mode: :local)

Advantages

  • Lower latency - No network overhead
  • Simpler setup - No server to run
  • Best for CI/CD - Headless automation
  • Multiple browsers - Chromium, Firefox, WebKit

Limitations

  • Browser runs on the same machine as your Elixir app
  • In WSL, headless-only (no visible window on Windows)

Windows Transport

The Windows transport (Playwriter.Transport.WindowsCmd) runs Playwright directly on Windows via PowerShell, controlled from WSL.

How It Works

Your Application (WSL)
      
      
Windows Transport (GenServer)
      
       Erlang Port (stdin/stdout)
      
PowerShell.exe
      
      
Node.js + Playwright (on Windows)
      
      
Browser (visible on Windows desktop)

Usage

# Visible browser on Windows
Playwriter.fetch_html("https://example.com", mode: :windows)

# Interactive session
Playwriter.with_browser([mode: :windows], fn ctx ->
  :ok = Playwriter.goto(ctx, "https://example.com")
  :ok = Playwriter.click(ctx, "button")
  Playwriter.content(ctx)
end)

Setup

One-time installation:

powershell.exe -ExecutionPolicy Bypass -File priv/scripts/start_server.ps1 -Install

Advantages

  • Visible browsers - See automation happening in real-time
  • No network issues - Bypasses WSL2 Hyper-V firewall completely
  • No server required - Just works after npm install
  • Best for WSL development - The recommended mode for WSL users

Limitations

  • Only works from WSL to Windows
  • Currently only supports Chromium
  • Slightly higher startup latency than local mode

Remote Transport

The remote transport (Playwriter.Transport.Remote) is designed for connecting via WebSocket to a Playwright server running elsewhere.

Current Status

Note: The remote transport is currently non-functional from WSL2 due to Hyper-V firewall blocking WebSocket connections to Windows. Use :windows mode instead for WSL-to-Windows automation.

The remote transport returns a helpful error message directing users to :windows mode:

{:error, {:not_supported, "Use mode: :windows instead..."}} =
  Playwriter.fetch_html(url, mode: :remote)

When Remote Would Be Useful

In non-WSL scenarios where you have network access to a Playwright server:

  • Distributed browser automation across machines
  • Controlling browsers in cloud environments
  • Separating browser execution from application logic

Choosing a Transport

ScenarioRecommended Transport
CI/CD pipelines:local (headless)
WSL development:windows
WSL debugging:windows
Docker containers:local
Production scraping:local (headless)
E2E test development:windows (see browser)

Custom Transports

You can implement custom transports for specialized scenarios:

defmodule MyApp.CustomTransport do
  @behaviour Playwriter.Transport.Behaviour

  use GenServer

  @impl true
  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts)
  end

  @impl true
  def init(opts) do
    # Your initialization logic
    {:ok, %{}}
  end

  @impl Playwriter.Transport.Behaviour
  def launch_browser(transport, browser_type, opts) do
    GenServer.call(transport, {:launch_browser, browser_type, opts})
  end

  # ... implement other callbacks
end

Use your custom transport:

Playwriter.with_browser([transport_module: MyApp.CustomTransport], fn ctx ->
  # Your automation code
end)

Troubleshooting

Local Transport Issues

"playwright_ex driver not found"

# Run setup
mix playwriter.setup

"Browser launch failed"

# Ensure dependencies are installed
cd deps/playwright/priv/static && npx playwright install-deps chromium

Windows Transport Issues

"Timeout waiting for transport to start"

  • Ensure Node.js is installed on Windows
  • Run the setup script: powershell.exe -ExecutionPolicy Bypass -File priv/scripts/start_server.ps1 -Install

"Cannot find module 'playwright'"

# On Windows, in PowerShell
cd $env:TEMP\playwriter-server
npm install playwright
npx playwright install chromium

Remote Transport Issues

"not_supported" error

  • Use mode: :windows instead when working from WSL
  • The remote transport doesn't work from WSL2 due to networking restrictions