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()) :: :okLocal 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
| Scenario | Recommended 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
endUse 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 chromiumRemote Transport Issues
"not_supported" error
- Use
mode: :windowsinstead when working from WSL - The remote transport doesn't work from WSL2 due to networking restrictions