New in reflex-enterprise v0.9.2.
End-to-End Testing
reflex-enterprise ships a pytest plugin that starts an existing Reflex app with
reflex run and passes the live URL to tests. Tests drive the running app —
frontend and backend — with browser automation such as
pytest-playwright.
The plugin manages the app process and returns its URL. Browser automation is chosen and controlled by the test suite.
Installation
The [testing] extra installs pytest, but does not include a browser driver.
pytest-playwright is used in the examples below, but any browser automation
framework that can drive a live URL is compatible.
The fixture is registered through a pytest11 entry point, so no
conftest.py configuration is required.
Quickstart
Place a test anywhere at or below the directory containing rxconfig.py:
Run it:
The first test that requests reflex_app starts the app. A cold start
compiles the frontend; subsequent runs reuse a cached working directory and
start quickly. The running app is shared by every test in the session and shut
down when the session ends.
How it works
- Discovery — the app under test is found by walking up from the test file
to the nearest
rxconfig.py. That directory is the app root. - Isolation — the app runs with relocated
REFLEX_WEB_WORKDIR/REFLEX_STATES_WORKDIR, telemetry disabled, in a working directory outside the checkout, so compiled frontend and state artifacts never land in the working tree.reflex runitself may still write areflex.locknext torxconfig.py, you may.gitignoreit for pure test apps. - Reuse and bounding — a session-scoped manager caches running apps by app
root and run mode. It will reuse healthy running apps keeping at most
max_appsalive, and evicting the least recently used to make room for a different app. Thereflex_appfixture is function-scoped but borrows from the manager, so apps outlive individual tests. - Ports —
reflex runpicks its own ports (auto-incrementing from 3000/8000) and the plugin reads the real URL back from its output. - Readiness — the fixture is fulfilled only when the frontend and the
backend are both accepting connections. If either fails to come up, the
fixture raises at setup with the captured
reflex runoutput in the message, and the test is reported as an error. The same contract governs reuse: a cached app whose frontend or backend is no longer reachable is torn down and restarted before being handed to a test.
The reflex_app fixture
The reflex_app fixture returns a ReflexApp object with a small, stable surface:
Reusing setup steps
Because every test starts from a clean state (see the warning above), any starting condition a test depends on — a logged-in session, a populated form, an open dialog — has to be re-established for that test. Factor those steps into a function-scoped fixture so each test body can assume the app is already in the state it cares about.
For example, a fixture that clicks through a menu to open a settings modal, so the test starts with the modal already open:
The fixture depends on reflex_app and page, both function-scoped, so it
runs once per test against that test's own client_token — the setup is
repeated for every test but the state never leaks between them. Fixtures like
this compose: a logged_in fixture can navigate from the login page, and
settings_modal can depend on it to open the modal as an authenticated user.
Debugging failures
A frontend assertion can fail because of a server-side problem — a startup warning, a compile error, a backend traceback during an event. The plugin surfaces the server output in three places:
- When a test fails, the captured
reflex runoutput of every app the test borrowed is attached to the report as a section, printed alongside the failure like captured stdout: - When the app fails to start, the
ReflexAppStartErrorraised during fixture setup embeds the full captured output, so the cause (bad rxconfig, missing dependency, port conflict) is part of the error. - Programmatic access —
reflex_app.logs()returns the same captured output for custom assertions or logging.
Configuration
Every option can be set via a command-line flag, a pytest ini option, or an environment variable. Precedence is CLI > env > ini > default.
run_mode— thereflex runenv mode:dev(default),prod, or a comma-separated list (--reflex-run-mode=dev,prod). With more than one mode, every test that usesreflex_appis parameterized to run once per mode, and tests are grouped so all of one mode run before the next mode starts. Dev and prod instances of the same app are cached separately (each counts towardmax_apps) with separate persistent workdirs, so their build outputs never mix. Tests can read the current mode via the session-scopedreflex_run_modefixture ("dev"or"prod"). Inprodmode reflex serves the frontend and backend on a single address, soreflex_app.backend_urlfalls back toreflex_app.url.workdir_strategy—persistentreuses a per-app, per-run-mode cache directory across sessions (warm starts, roughly 6x faster than cold);tmpuses a fresh temporary directory each start (clean but always cold).reflex_run_args— extra CLI arguments appended toreflex run.--frontend-only/--backend-onlyare rejected immediately, since a partial run can never satisfy the readiness check. The plugin appends its own--env <mode>after these args, so an--envhere is overridden — set the mode with--reflex-run-modeinstead.- Share/isolate
REFLEX_DIR— by default the global bun/node/reflex dependencies are shared with the host to avoid a slow re-download. Pass--reflex-isolate-reflex-dirfor a fully hermetic (slower) run, or--reflex-share-reflex-dirto force sharing over an ini/env setting (CLI wins either way).
Example pyproject.toml:
Tuning settings from a fixture
For anything the static options can't express, the session-scoped
reflex_app_manager fixture returns the AppManager, whose settings
attribute (a ReflexAppTestSettings) is the supported integration point —
mutate it from your own fixture. Settings are consumed when an app starts, so
apply changes before the first reflex_app use, e.g. in an autouse session
fixture:
ReflexAppTestSettings is a dataclass holding the resolved value of every
option from the table above (CLI/env/ini already applied), plus the same
defaults when unset:
AppManager, ReflexApp, and ReflexAppTestSettings are all importable from
reflex_enterprise.testing for proper typing of fixtures like the one above.
Measuring coverage
Because the app code executes in the reflex run subprocess started by the
fixture, pytest itself may not import or execute any app code directly. To
record coverage requires enabling coverage.py's subprocess patch to measure
the child process data and combine it into the final report.
Configure it in pyproject.toml — for an app named my_app:
Then run with pytest-cov:
Each app subprocess writes its own .coverage.* data file; pytest-cov
combines them when the session ends and reports coverage for the app package.
Notes
- pytest-xdist — each worker process gets its own manager, so the
effective cap on running apps is
max_apps * num_workers. - Concurrent
pytestruns — inpersistentmode each app's workdir carries a pid lockfile. If anotherpytestprocess is already using an app's workdir, a second run transparently falls back to its own throwaway workdir so the two never recompile or clobber the same.web. Stale locks (from a crashed run) are reclaimed automatically. frontend_path—reflex_app.urlhonors a configuredfrontend_path(it is read from reflex's own "App running at:" line).- The plugin starts apps one at a time and waits for readiness, which is
required for
reflex run's port auto-increment to work correctly when more than one app runs.