Skip to content

Browser-test fixture: built-in /_browser/login-as sets bare session.userId / session.userEmail, no extension point for app session shape #2830

Description

@bpamiri

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:

  1. 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).

  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions