Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixed

- Start Volt's application before the browser a test run shares, which lives under its supervisor. A project that depends on Volt with `runtime: false` had to start it in `test_helper.exs`, or browser tests failed with `Volt.Supervisor` not running.

## 0.22.0 - 2026-10-07

### Added
Expand Down
2 changes: 1 addition & 1 deletion guides/features/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ npx playwright install chromium

By default Volt uses `node_modules/playwright/cli.js` when present, otherwise `playwright` from `PATH`. Pass PlaywrightEx supervisor options with `playwright: [...]` if your executable lives elsewhere.

A test run shares one browser, launched the first time a test needs it, and every test gets a browser context of its own, so tests do not see each other's storage, cookies or pages. The ExUnit modules Volt generates for browser tests are `async: true`.
A test run shares one browser, launched the first time a test needs it, and every test gets a browser context of its own, so tests do not see each other's storage, cookies or pages. The ExUnit modules Volt generates for browser tests are `async: true`. The browser runs under Volt's application, which the first browser test starts if the project has not, as with a `runtime: false` dependency.

## ExUnit integration

Expand Down
18 changes: 13 additions & 5 deletions lib/volt/test/browser/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ defmodule Volt.Test.Browser.Supervisor do

@playwright Volt.Test.Browser.Playwright

@doc "Start the tree, or find it already running."
@doc """
Start the tree, or find it already running.

The tree lives under `Volt.Supervisor`, so Volt's application is started
first: a project that depends on Volt with `runtime: false` has not started
it.
"""
@spec start(Config.t()) :: {:ok, pid()} | {:error, term()}
def start(%Config{} = config) do
case Supervisor.start_child(Volt.Supervisor, {__MODULE__, config}) do
{:ok, pid} -> {:ok, pid}
{:error, {:already_started, pid}} -> {:ok, pid}
{:error, reason} -> {:error, reason}
with {:ok, _started} <- Application.ensure_all_started(:volt) do
case Supervisor.start_child(Volt.Supervisor, {__MODULE__, config}) do
{:ok, pid} -> {:ok, pid}
{:error, {:already_started, pid}} -> {:ok, pid}
{:error, reason} -> {:error, reason}
end
end
end

Expand Down
18 changes: 18 additions & 0 deletions test/volt/test/browser_runner_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ defmodule Volt.Test.BrowserRunnerTest do
assert :ok = apply(module, name, [%{}])
end

test "starts Volt's application for a project that does not", %{tmp_dir: tmp_dir} do
file =
write!(tmp_dir, "stopped.browser.test.ts", ~TS"""
import { test, expect } from 'volt:test'

test('runs', () => {
expect(document.body).toBeTruthy()
})
""")

config = Volt.Test.Config.read(browser: true, root: tmp_dir)
:ok = Application.stop(:volt)
on_exit(fn -> Application.ensure_all_started(:volt) end)

assert {:ok, %Result{status: :passed}} =
Volt.Test.BrowserRunner.run_test(file, 1, config: config)
end

test "browser test modules run concurrently", %{tmp_dir: tmp_dir} do
write!(tmp_dir, "async.browser.test.ts", ~TS"""
import { test, expect } from 'volt:test'
Expand Down
Loading