Skip to content

fix(test): resolve 20 browser-spec errors with root-cause fixes#2134

Merged
bpamiri merged 2 commits intodevelopfrom
peter/test-infra-browser-ci-skip
Apr 17, 2026
Merged

fix(test): resolve 20 browser-spec errors with root-cause fixes#2134
bpamiri merged 2 commits intodevelopfrom
peter/test-infra-browser-ci-skip

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented Apr 17, 2026

Summary

Resolves the 20 browser-test errors that surface when running bash tools/test-local.sh on Lucee 7 + SQLite. Prior state: 3063 pass / 0 fail / 20 error. New state: 3067 pass / 0 fail / 0 error. All 119 browser specs (BrowserDialog/Integration/Launcher/Login/Route/TestLifecycle) are now green.

Three distinct root causes, each fixed directly — no skip/gate workarounds.

1. Lucee 7 rejects struct-based createDynamicProxy targets (8 dialog errors)

BrowserClient.$requireDialogSupport() and $registerDialogListener() passed an inline struct with an accept closure as the proxy target. Lucee 7 tightened the first-argument type to require a Component — structs now fail with "Can't cast Complex Object Type Struct to String" as Lucee tries the CFC-path-string overload first.

Fix: new wheels.wheelstest.DialogConsumer CFC that encapsulates the Consumer<Dialog> handler logic, parameterized with the caller's state and action structs. Both the probe and the real handler now pass a CFC instance. Probe also corrected from java.lang.Runnable (method run()) to java.util.function.Consumer (method accept(T)) to match the interface actually used in dialog handling.

2. Named fixture routes not visible during core tests (5 route errors)

Two sub-problems:

a) vendor/wheels/tests/routes.cfm (the core test runner's route file) did not declare the _browser/* fixture routes — only the app's config/routes.cfm did.

b) Other specs in the core suite (mapperSpec, security/PaginationXssSpec, view/linksSpec, view/formsSpec) legitimately call $clearRoutes() to test route-registration behavior and do not restore afterwards. Bundles run alphabetically, so those specs wipe the route table before browser specs execute.

Fix A: register _browser/* fixture routes in vendor/wheels/tests/routes.cfm.
Fix B: BrowserTest.beforeAll() re-includes the test routes file and calls $setNamedRoutePositions(), making browser specs self-contained regardless of earlier specs' route-wiping.

3. Fixture controllers + views only existed in the app tree (7 login errors)

The core test runner swaps controllerPath to /wheels/tests/_assets/controllers, so the app-tree BrowserTestHome.cfc, BrowserTestLogin.cfc, BrowserTestSessions.cfc and their views are invisible during core tests. Playwright requests returned empty responses, failing assertions like "Expected page to contain 'alice@example.com'".

Fix: copied the three fixture controllers + six view templates into vendor/wheels/tests/_assets/. The loginAs env-gate in the app copy references application.$wheels which is cleared post-init — the vendor copy omits the gate since these routes are only loaded by the core test runner.

Also

  • tools/test-local.sh exports WHEELS_BROWSER_TEST_BASE_URL=http://localhost:${PORT} so Playwright hits the running server instead of the default localhost:8080. CI's own export is preserved via the ${VAR:-default} pattern.

Test plan

  • PORT=9090 bash tools/test-local.sh on Lucee 7 + SQLite: 3067 pass / 0 fail / 0 error (was 3063 pass / 0 fail / 20 error).
  • Verified all 6 browser bundles green: BrowserDialog (9/9), BrowserIntegration (63/63), BrowserLauncher (26/26), BrowserLogin (7/7), BrowserRoute (5/5), BrowserTestLifecycle (9/9).
  • Confirmed non-browser specs unaffected by route re-registration in BrowserTest.beforeAll — full suite passes cleanly.
  • Confirmed DialogConsumer CFC works when multiple dialog assertions run sequentially (probe caches the $dialogSupported=true result on first call).

Why not just skip?

Skipping via WHEELS_CI=true masked three real bugs:

  • The Lucee 7 struct-proxy incompatibility would have continued to break any code path that tried to proxy a CFML struct to a Java interface.
  • The fixture-route gap would have left a latent coupling between app routes and core tests.
  • The test-asset controllers being missing was a long-standing drift from when the app fixtures were introduced.

Each root-cause fix unlocks the actual capability rather than hiding the symptom.

🤖 Generated with Claude Code

@bpamiri bpamiri marked this pull request as draft April 17, 2026 01:15
Restores all 119 browser specs (BrowserDialog/Integration/Launcher/
Login/Route/TestLifecycle) to green when running
`bash tools/test-local.sh` on Lucee 7 + SQLite. Prior state: 3063 pass,
0 fail, 20 error. New state: 3067 pass, 0 fail, 0 error.

Three distinct root causes, fixed independently:

1. Lucee 7 rejects struct-based createDynamicProxy targets.
   `BrowserClient.\$requireDialogSupport()` and `\$registerDialogListener()`
   passed an inline struct with an `accept` closure as the proxy target.
   Lucee 7 tightened the first-argument type to require a Component —
   structs now fail with "Can't cast Complex Object Type Struct to
   String" as Lucee tries the CFC-path-string overload first.

   Fix: new `wheels.wheelstest.DialogConsumer` CFC that encapsulates
   the Consumer<Dialog> handler logic, parameterized with the caller's
   `state` and `action` structs. Both the probe and the real handler
   now pass a CFC instance; closures live inside the CFC. Probe also
   switched from `java.lang.Runnable` (method: `run()`) to
   `java.util.function.Consumer` (method: `accept(T)`) — matches the
   interface used in real handling. Error messages now include the
   underlying Lucee exception in `extendedInfo` for future debugging.

2. Named fixture routes (browserTestHome, browserTestLogin, etc.) are
   not visible during core tests.
   The core test runner includes `vendor/wheels/tests/routes.cfm`
   before test execution, but prior to this PR that file did not
   declare the `_browser/*` fixture routes — only the app's
   `config/routes.cfm` did. Additionally, other specs in the core
   suite (mapperSpec, security/PaginationXssSpec, view/linksSpec,
   view/formsSpec) legitimately call `\$clearRoutes()` to test
   route-registration behavior and do not restore the table after.
   Since bundles run alphabetically, those specs wipe the routes
   before browser specs execute.

   Fix A: register `_browser/*` fixture routes in
   `vendor/wheels/tests/routes.cfm` so core tests can resolve them.
   Fix B: `BrowserTest.beforeAll()` re-includes the test routes file
   and calls `\$setNamedRoutePositions()`. This makes browser specs
   self-contained regardless of earlier specs' route-wiping.

3. Fixture controllers + views live only in the app tree.
   The core test runner swaps `controllerPath` to
   `/wheels/tests/_assets/controllers`, so app-tree controllers
   (`BrowserTestHome.cfc`, `BrowserTestLogin.cfc`,
   `BrowserTestSessions.cfc`) and their views are invisible during
   core tests. Playwright-driven test requests returned empty
   responses, failing assertions like "Expected page to contain
   'alice@example.com'".

   Fix: copy the three fixture controllers + six view templates into
   `vendor/wheels/tests/_assets/controllers/` and
   `vendor/wheels/tests/_assets/views/browsertest*/`. The loginAs
   env-gate in the app copy references `application.\$wheels` which is
   cleared post-init; the vendor copy omits the gate since the test
   routes are only loaded by the core test runner.

Also:
- `tools/test-local.sh` exports `WHEELS_BROWSER_TEST_BASE_URL` to
  match the local `\$PORT` so Playwright hits the running server
  instead of the default localhost:8080. CI's own export is preserved
  via the `\${VAR:-default}` pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bpamiri bpamiri force-pushed the peter/test-infra-browser-ci-skip branch from 132e8d0 to 37dcb33 Compare April 17, 2026 01:40
@bpamiri bpamiri changed the title test: skip browser specs by default in test-local.sh fix(test): resolve 20 browser-spec errors with root-cause fixes Apr 17, 2026
@bpamiri bpamiri marked this pull request as ready for review April 17, 2026 01:41
Documents the three root causes fixed in this PR so future contributors
don't hit the same traps:

- .ai/wheels/cross-engine-compatibility.md gains a new section on
  Lucee 7's tightened `createDynamicProxy` signature — structs no
  longer accepted, must be CFC instances. Includes the misleading
  error message ("Can't cast Struct to String") and the CFC-with-init
  pattern from DialogConsumer.

- .ai/wheels/testing/browser-testing.md updates two existing gotchas:
  - `createDynamicProxy` entry now distinguishes engine support
    (Lucee-only) from the Lucee 6→7 argument-type tightening.
  - Fixture routes entry explains the two-place requirement (app tree
    for app tests + vendor tree for core tests) and the same split
    for controllers and views.

  Adds a new gotcha on sibling specs (`mapperSpec`, `PaginationXssSpec`,
  `view/linksSpec`, `view/formsSpec`) that call `$clearRoutes()` in
  `beforeEach`/`beforeAll` without restoring — bundles run
  alphabetically, so later specs that need named routes find an empty
  table. The defensive fix (`beforeAll` re-include) is documented as
  the pattern for route-dependent specs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added the docs label Apr 17, 2026
@bpamiri bpamiri merged commit cf760bd into develop Apr 17, 2026
3 checks passed
@bpamiri bpamiri deleted the peter/test-infra-browser-ci-skip branch April 17, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant