LiveKit RPC UI synchronization testing proves that an RPC request, its terminal result, the authoritative agent state, and the browser's rendered state agree. A callback can succeed while the UI still shows a stale spinner, two result cards, the wrong active speaker, or a control that should already be disabled.
Definition: A synchronized LiveKit agent UI renders the latest valid session state once, survives duplicate or late events, and can reconstruct the same view after reconnecting.
Quick filter: If your LiveKit agent has no visual client, use the broader complete LiveKit testing guide. Use this checklist when RPC results or agent state change what a caller or supervisor sees and can do.
TL;DR: Keep fast agent tests and RPC contract tests below a small browser layer. For every critical RPC, assert the request, success or typed error, state transition, visible UI, duplicate behavior, and reconnect result. Block release when the terminal UI disagrees with the session state, even if every callback completed.
Methodology: This checklist applies the official LiveKit contracts linked below, checked on September 25, 2026. Suggested test counts and CI placement are starter policies, not measured benchmarks. Increase coverage for payments, healthcare, identity, and other high-impact workflows.
Last Updated: September 2026
Related Guides:
- Complete LiveKit Voice Agent Testing Guide - logic, audio, WebRTC, load, and production layers
- Voice Agent Tool-Call Contract Template - backend requests, results, and side effects
- Voice Agent Sandbox Testing - integration fixtures and cleanup
- Voice Agent Tests as Code - versioned scenarios and release gates
- Voice Agent CI/CD Testing - delivery-pipeline coverage
- WebRTC Troubleshooting Guide - transport and media failures
- OpenTelemetry for Voice Agents - correlated client, agent, and tool evidence
- Testing and Monitoring LiveKit Agents - production coverage
What Should LiveKit RPC UI Synchronization Tests Prove?
They should prove one outcome across four independently testable layers.
| Layer | Question | Fastest useful test | Do not mistake it for |
|---|---|---|---|
| Agent behavior | Did the agent choose the correct message, tool, or handoff? | Python or Vitest agent test | browser or transport coverage |
| RPC contract | Did the correct participant receive a valid method and payload, then return the expected result or error? | SDK integration test with a fake receiver | proof that React rendered it |
| State projection | Did the application reduce events into one valid UI state? | reducer or store test with reordered events | proof that reconnect works |
| Browser behavior | Did a user see and control the right thing through the real lifecycle? | one deterministic browser E2E per critical journey | exhaustive logic coverage |
LiveKit's agent test framework supports Python and Vitest tests for messages, tool calls, and handoffs. Keep those tests fast. They are the foundation, not a substitute for the RPC and browser boundaries.
A reconnect or network delay can change arrival order while the correct terminal state stays the same. Asserting one canonical event sequence can reject a valid outcome. Assert allowed transitions and final state, with ordering assertions where order changes safety or meaning.
How Do You Test the LiveKit RPC Contract?
LiveKit RPC pairs a method registered by one participant with performRpc from another. Treat participant identity, method name, payload version, timeout, and error mapping as public API fields.
| Contract field | Passing assertion | Required negative case |
|---|---|---|
| Destination | Intended participant receives one request | recipient missing or disconnected |
| Method | Registered method matches the action | unsupported method |
| Payload | Schema and version parse before business logic | malformed, unknown version, or oversized payload |
| Correlation | Request ID appears in response, state update, and client evidence | response with unknown request ID |
| Timeout | UI exits pending state and offers a safe next action | response arrives after timeout |
| Error | Typed error maps to a specific user-safe state | raw internal error reaches the UI |
| Retry | Idempotent request does not duplicate visible or durable state | same request ID sent twice |
The current LiveKit documentation sets a 15 KiB payload ceiling, a 64-byte method-name limit, and a 10-second default timeout. Test below and above the limits you actually use; link the failure to a visible recovery state instead of checking only the exception.
Use a small, versioned envelope so the browser can reject ambiguous events:
{ "version": 1, "requestId": "search-042", "action": "lookup_order", "status": "found", "entityId": "order-fixture-17", "occurredAt": "2026-08-12T18:30:00Z"}
The identifier is synthetic. In real evidence, redact customer data and preserve stable pointers rather than copying sensitive payloads into screenshots or reports.
How Do You Prove the LiveKit UI Is Synchronized?
Assert from an authoritative state model, not directly from every incoming event. An RPC response proves that an operation completed. It is not automatically the whole screen state.
LiveKit's agent-state guide documents states including connecting, initializing, listening, thinking, speaking, disconnected, and failed. It recommends convenience getters such as isConnected, isPending, and isFinished for common UI decisions. Custom workflow state can be synchronized separately.
| State fact | UI assertion | Failure caught |
|---|---|---|
| Session is pending | Connection-dependent controls remain unavailable | RPC sent before a destination exists |
| Agent is listening | Listening indicator is visible once | duplicate event renders duplicate UI |
| Agent is thinking | Input policy matches the product decision | stale speaking state blocks interruption |
| RPC lookup succeeded | Spinner disappears and one result card appears | callback succeeds but store never updates |
| RPC failed or timed out | Pending UI clears and a recovery action appears | indefinite spinner or false success |
| Session finished | Controls are disabled and terminal content remains | late response reopens a completed workflow |
Test selectors against meaning: a labeled status, enabled control, result count, or error action. Avoid tying assertions to animation timing or CSS classes when the user-visible contract is what matters.
Synchronization invariant: For one room, participant, request, and state version, the store reaches one valid terminal state and the browser renders its matching controls and content exactly once.
Which Race Conditions Need Blocking Tests?
Start with failures that create a believable but wrong interface. A stale spinner hides completion; a duplicated confirmation can make one action look like two successful actions. A test that checks only the callback result can miss both defects.
| Injection | Expected terminal result | Block release when |
|---|---|---|
| Duplicate success | one result and one completed request | two cards, toasts, or side effects appear |
| Success after client timeout | timeout policy or documented reconciliation wins | UI silently flips from failure to success |
| Older state after newer state | higher accepted version remains authoritative | status moves backward |
| Participant reconnect | UI rebuilds from current session state | controls stay disabled or old data survives |
| RPC during disconnect | request is rejected or explicitly queued | request disappears without a terminal outcome |
| Session ends while RPC is pending | terminal session state wins | late response revives controls or spinner |
| Error then retry success | one final success with prior error cleared | stale error and success render together |
LiveKit sessions provide the lifecycle boundary for creating, starting, interacting with, and ending a session. Build reconnect tests around that lifecycle rather than simulating a component remount. Capture the state and error events described in the agent event reference when diagnosis needs event-level evidence.
What Is the Smallest Useful Browser E2E Test?
Use one deterministic fixture that crosses the whole seam:
- Start a session with a synthetic participant and known room metadata.
- Wait for the connected state; do not use a fixed sleep.
- Trigger
lookup_orderwith request IDsearch-042. - Assert one pending indicator and disabled duplicate-submit control.
- Return the versioned
foundresponse and matching state update. - Assert the spinner disappears, one result card renders, and controls are correct.
- Replay the response; assert the result count stays at one.
- Disconnect and reconnect; assert the same terminal view reconstructs.
- End the session; assert controls remain disabled after a late response.
- Save room, participant, request, state version, console error, and trace identifiers on failure.
Use deterministic data and state-based waits. Do not depend on a model choosing the same prose twice. Keep model behavior in the agent-test layer and the browser test focused on synchronization.
Where Should These Tests Run?
| Pipeline | Coverage | Suggested gate |
|---|---|---|
| Pull request | state reducer, payload schema, RPC success, error, and timeout | every RPC or UI boundary change |
| Pull request, targeted | one browser happy path and highest-risk race | session, state, or rendering changes |
| Scheduled | reconnect, duplicate, late response, and browser matrix | daily for an active surface |
| Release | critical journeys against the release candidate | no unresolved terminal-state mismatch |
| Production monitoring | RPC errors, timeouts, stale pending state, client exceptions | sustained user-impacting deviation |
When production reveals a mismatch, export the smallest correlated evidence packet and turn it into a regression case. The tests-as-code template preserves that case; the OpenTelemetry guide connects browser, RPC, agent, and tool spans.
What Are the Limits of This Checklist?
Passing these tests does not prove the audio experience is good. It does not measure latency, interruption quality, packet loss, ASR accuracy, or whether a backend side effect happened once. Use real-stream testing for the media path and the tool-call contract template for durable business state.
Browser tests are expensive. Do not reproduce every schema permutation in Playwright. Keep exhaustive contract cases below the browser, then reserve E2E coverage for lifecycle seams and visual outcomes that cheaper tests cannot prove.
One tension remains: some products prefer optimistic UI while others wait for authoritative agent state. Either policy can work. What cannot remain ambiguous is which state wins after timeout, reconnect, or a late response.
LiveKit RPC UI Release Checklist
- Schemas: Critical methods have versioned request and response schemas.
- Correlation: Participant, method, request, room, and state identifiers correlate.
- Negative cases: Unsupported method, missing recipient, malformed payload, timeout, and disconnect are tested.
- Terminal state: Pending UI reaches success, recoverable error, or terminal session state.
- Ordering: Duplicate and out-of-order events cannot duplicate or reverse visible state.
- Reconnect: The view reconstructs without replaying side effects.
- Teardown: Late responses cannot revive a finished session.
- Waits: Browser tests use observable state instead of fixed delays.
- Evidence: Failures save redacted client, RPC, session, and trace evidence.
- Coverage boundary: Real-stream and side-effect tests cover what this checklist does not.

