Available since v0.1.0
Phoenix LiveView
StablePhoenix LiveView keeps a persistent WebSocket connection to the server and pushes HTML diffs as state changes. The client morphs them into the DOM via its own morphing implementation — identity is preserved across updates. fs-* attributes live in your HEEx templates and survive morph updates.
Install
Load the agent in your root layout (lib/my_app_web/components/layouts/root.html.heex):
<head>
<script phx-track-static type="text/javascript" src={~p"/assets/faultsense-agent.min.js"} defer />
<script phx-track-static type="text/javascript" src={~p"/assets/app.js"} defer />
<script>
document.addEventListener("phx:page-loading-stop", () => {
Faultsense.init({
releaseLabel: "<%= System.get_env("RELEASE_LABEL") %>",
collectorURL: "/events",
apiKey: "<%= System.get_env("FAULTSENSE_KEY") %>",
});
});
</script>
</head>
Use phx:page-loading-stop to initialize — it fires after LiveView has settled the initial mount.
Morph updates — use updated
LiveView's client morphs incoming diffs into the DOM, preserving element identity where possible. The canonical assertion type is fs-assert-updated — elements are patched in place, not added.
This is the same shape as Livewire (PAT-04 morphdom preserved-identity).
<button
phx-click="increment"
fs-assert="counter/increment"
fs-trigger="click"
fs-assert-updated="#count[text-matches=\d+]">
+
</button>
<span id="count"><%= @count %></span>
phx-click and actions
<button
phx-click="delete_todo"
phx-value-id={@todo.id}
fs-assert="todos/delete"
fs-trigger="click"
fs-assert-removed={".todo-item[data-todo-id='#{@todo.id}']"}>
Delete
</button>
phx-click sends an event over the WebSocket; the server re-renders; the client morphs the response. A deletion removes the element — removed fires.
Form handling
<.form for={@form} phx-submit="save">
<input name={@form[:email].name} value={@form[:email].value}
fs-assert="signup/email-valid"
fs-trigger="blur"
fs-assert-updated=".email-field[classlist=valid:true]" />
<button type="submit"
fs-assert="signup/submit"
fs-trigger="click"
fs-assert-visible=".welcome">
Sign up
</button>
</.form>
Dynamic assertion values
HEEx lets you interpolate expressions inside attributes:
<input type="checkbox"
phx-click="toggle"
phx-value-id={@todo.id}
checked={@todo.completed}
fs-assert="todos/toggle"
fs-trigger="change"
fs-assert-updated={".todo-item[classlist=completed:#{[email protected]}]"} />
LiveView stream patches
Phoenix.LiveView.stream/3 manages large lists efficiently by maintaining a keyed structure and pushing only deltas. New items are inserted, removed items are deleted — but identity is preserved for untouched rows. Use fs-assert-added for new stream entries, fs-assert-removed for removals.
Live navigation (live_redirect, live_patch)
Live navigation updates the URL and LiveView state without a full page load. The socket stays open; the agent session stays alive. Don't use fs-assert-mpa="true" with live navigation — the agent never sees an unload. Regular DOM assertions work across live nav.
Gotchas
- Use
updated, notadded, for in-place mutations. LiveView morphs. phx-update="append"vsphx-update="prepend"vsphx-update="replace"determine the DOM shape.append/prependadd children (useadded);replaceclears and refills (useaddedon new children, orupdatedon the parent).- JS hooks (
phx-hook) run custom JS per element. They can mutate the DOM in ways LiveView doesn't know about — the agent still observes via MutationObserver, so assertions still resolve. - Stream containers need
phx-update="stream"on the parent. This enables the keyed diffing that LiveView uses forstream/3collections.
See also
- Frameworks index
- Livewire notes — similar patterns apply