Summary
The built-in /_browser/login-as fixture mounted when application.wheels.loadBrowserTestFixtures = true calls into vendor/wheels/public/browser-fixtures/controllers/BrowserTestLogin.cfc#create(), whose entire body sets:
session.userId = 1;
session.userEmail = params.identifier;
This shape is fine for the framework's own demo specs (see vendor/wheels/tests/specs/wheelstest/BrowserLoginSpec.cfc) but is incompatible with any real app whose authentication writes a richer session structure — e.g. session.member = { id, email, firstName, lastName }, plus app-specific session enrichment.
There's no extension point for an app that opts into loadBrowserTestFixtures = true to also have its app-specific session keys populated by the fixture. The only paths today:
- Don't opt in to the framework fixture and write your own controller + route at the same
/_browser/login-as path before the framework include. This is what we did. Works, but means every Wheels app that wants browser-test auth duplicates the route + controller + environment-gating boilerplate. It also can't easily co-exist with the framework fixture if a plugin later wants to use the built-in path.
- Patch the framework's
BrowserTestLogin.cfc to also set app-specific session keys. Brittle (vendor-tree changes get clobbered on upgrade) and doesn't compose if multiple plugins want a say.
Why it matters
BrowserClient.loginAs(identifier) (in vendor/wheels/wheelstest/BrowserClient.cfc:547-550) is the documented DSL entrypoint for browser-test auth, but it points at a fixture whose session shape doesn't match real apps. The result is: most production-app teams writing browser specs will eventually need to abandon loadBrowserTestFixtures=true and re-implement the same login fixture themselves.
Suggestion
A configurable callback or override hook. Two shapes that would work:
-
Settings-driven handler. set(browserLoginAsHandler = "AuthFixture##loginAs") in config/settings.cfm tells the framework to delegate /_browser/login-as to the app's controller instead of running BrowserTestLogin#create. The framework route still mounts and still env-gates, but body execution is the app's. Handler receives (identifier) (plus any additional query params the BrowserClient DSL adds, e.g. our &isAD=false).
-
Lifecycle hook. A $afterBrowserLogin(identifier) callback the framework calls after setting its own session.userId/session.userEmail. The app fills in its richer session state on top. Less invasive but only works if your app's session shape extends the fixture's; doesn't help apps that need different/no top-level userId.
(1) is more flexible and matches existing Wheels convention-with-override patterns. Either way, docs at https://guides.wheels.dev/v4-0-0/testing/browser-testing/ should call out that the built-in fixture's session shape is {userId, userEmail} so app authors know upfront whether the fixture is usable for them.
Discovered during
Phase 1 of an internal RBAC portal build on Wheels 4.0.2 / Lucee 7. We ended up shipping a custom BrowserLoginAs.cfc controller mounted at /_browser/login-as (registered before the namespace block in config/routes.cfm) that sets session.member, session.isADAuth, and calls our app's session-enrichment helper. The custom controller env-gates identically to the framework's. Pattern is in https://github.com/paiindustries/titan/pull/3337 (private). Happy to extract specific snippets if useful for spec design.
Summary
The built-in
/_browser/login-asfixture mounted whenapplication.wheels.loadBrowserTestFixtures = truecalls intovendor/wheels/public/browser-fixtures/controllers/BrowserTestLogin.cfc#create(), whose entire body sets:This shape is fine for the framework's own demo specs (see
vendor/wheels/tests/specs/wheelstest/BrowserLoginSpec.cfc) but is incompatible with any real app whose authentication writes a richer session structure — e.g.session.member = { id, email, firstName, lastName }, plus app-specific session enrichment.There's no extension point for an app that opts into
loadBrowserTestFixtures = trueto also have its app-specific session keys populated by the fixture. The only paths today:/_browser/login-aspath before the framework include. This is what we did. Works, but means every Wheels app that wants browser-test auth duplicates the route + controller + environment-gating boilerplate. It also can't easily co-exist with the framework fixture if a plugin later wants to use the built-in path.BrowserTestLogin.cfcto also set app-specific session keys. Brittle (vendor-tree changes get clobbered on upgrade) and doesn't compose if multiple plugins want a say.Why it matters
BrowserClient.loginAs(identifier)(invendor/wheels/wheelstest/BrowserClient.cfc:547-550) is the documented DSL entrypoint for browser-test auth, but it points at a fixture whose session shape doesn't match real apps. The result is: most production-app teams writing browser specs will eventually need to abandonloadBrowserTestFixtures=trueand re-implement the same login fixture themselves.Suggestion
A configurable callback or override hook. Two shapes that would work:
Settings-driven handler.
set(browserLoginAsHandler = "AuthFixture##loginAs")inconfig/settings.cfmtells the framework to delegate/_browser/login-asto the app's controller instead of runningBrowserTestLogin#create. The framework route still mounts and still env-gates, but body execution is the app's. Handler receives(identifier)(plus any additional query params the BrowserClient DSL adds, e.g. our&isAD=false).Lifecycle hook. A
$afterBrowserLogin(identifier)callback the framework calls after setting its ownsession.userId/session.userEmail. The app fills in its richer session state on top. Less invasive but only works if your app's session shape extends the fixture's; doesn't help apps that need different/no top-leveluserId.(1) is more flexible and matches existing Wheels convention-with-override patterns. Either way, docs at https://guides.wheels.dev/v4-0-0/testing/browser-testing/ should call out that the built-in fixture's session shape is
{userId, userEmail}so app authors know upfront whether the fixture is usable for them.Discovered during
Phase 1 of an internal RBAC portal build on Wheels 4.0.2 / Lucee 7. We ended up shipping a custom
BrowserLoginAs.cfccontroller mounted at/_browser/login-as(registered before the namespace block inconfig/routes.cfm) that setssession.member,session.isADAuth, and calls our app's session-enrichment helper. The custom controller env-gates identically to the framework's. Pattern is in https://github.com/paiindustries/titan/pull/3337 (private). Happy to extract specific snippets if useful for spec design.