Wallabidi.Test.AwaitMonitor (wallabidi v0.4.1)

Copy Markdown View Source

Detects event-driven-await regressions per test.

Wallabidi's interactions are event-driven: visit, click, fill_in, assert_has, await_patch resolve when a browser event fires (onPatchEnd, a MutationObserver hit, a page-ready notification), not by polling or sleeping. When the event mechanism silently breaks, an await falls back to its timeout and the test often still passes — a later retry happens to find the element — so the regression hides behind a green checkmark.

This module catches that. Event-driven await operations call record_timeout/1 on the branch where a fire was expected but the deadline elapsed. The Wallabidi.Feature setup drains the current test's records in an on_exit and fails the test if any are present — so "this test only passed by timing out" surfaces as a real failure, in place.

Timeouts that are expected (waiting for absence: refute_has, visible?: false) must NOT be recorded — those legitimately wait out the budget.

Lifecycle

Enabled only in the test environment, by calling setup/0 from test_helper.exs. Until then (and in production), record_timeout/1 and drain/1 are cheap no-ops — the lib/ await code can call record_timeout/1 unconditionally with zero production cost or coupling.

Records are keyed by the test process pid (await ops run in the test process, so self() is the test). Wallabidi.Feature captures that pid at setup and passes it to drain/1 from on_exit (which runs in a different process).

Mode

WALLABIDI_AWAIT_MODE=warn records and reports but does not fail — for a validation pass before the detector gates CI. Default is :raise.

Summary

Functions

Called from a test's on_exit. Raises (or warns) if the test recorded event-driven-await timeouts. No-op when not enabled, no timeouts were recorded, or the test opted out.

Return and clear the recorded timeouts for test_pid. Returns [] when the monitor isn't enabled or the test had none.

Whether the monitor is active (table exists). False in production.

:raise (default) or :warn, from WALLABIDI_AWAIT_MODE.

Record that an event-driven await op fell back to its timeout in the current test process. No-op when the monitor isn't enabled.

Create the ETS table. Call once from test_helper.exs (test env only).

Functions

check!(test_pid, context \\ %{})

Called from a test's on_exit. Raises (or warns) if the test recorded event-driven-await timeouts. No-op when not enabled, no timeouts were recorded, or the test opted out.

context is the ExUnit test context — used for the test name and to honor the @tag :expected_await_timeout opt-out, for tests where an await-timeout is structural (e.g. a fake/non-LiveView page that has no real event to fire, so the await legitimately waits out its budget). Always drains the records (so they don't leak into the next test on a reused pid), even when opted out.

drain(test_pid)

Return and clear the recorded timeouts for test_pid. Returns [] when the monitor isn't enabled or the test had none.

enabled?()

Whether the monitor is active (table exists). False in production.

mode()

:raise (default) or :warn, from WALLABIDI_AWAIT_MODE.

record_timeout(op)

Record that an event-driven await op fell back to its timeout in the current test process. No-op when the monitor isn't enabled.

op is a short term identifying the operation, e.g. :patch, :page_ready, :selector, {:selector, query_desc}.

setup()

Create the ETS table. Call once from test_helper.exs (test env only).