Skip to content

COLDBOX-1457 Browser testing for ColdBox apps: @browser on BaseTestCase and named route helpers - #708

Merged
lmajano merged 14 commits into
developmentfrom
claude/bx-playwright-research-plan-ddfqpm
Oct 10, 2026
Merged

lmajano merged 14 commits into
developmentfrom
claude/bx-playwright-research-plan-ddfqpm

Conversation

@lmajano

@lmajano lmajano commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Description

Jira Issues

COLDBOX-1457. It needs TestBox's annotation-driven browser support, Ortus-Solutions/TestBox#222, which builds on TESTBOX-470 and Ortus-Solutions/TestBox#212.

This PR adds browser testing for ColdBox applications. It builds on TestBox browser support and the bx-playwright BoxLang module.

No BrowserTestCase: annotate BaseTestCase instead (BoxLang)

TestBox now attaches browser support to any spec whose class (or a parent class) has @browser, @browserProfile or @baseURL (Ortus-Solutions/TestBox#222). So a ColdBox browser test is just a BaseTestCase:

@appMapping( "/root" )
@browser
@baseURL( "http://127.0.0.1:8080" )
class extends="coldbox.system.testing.BaseTestCase" {
    function run() {
        it( "shows a user", () => {
            browse( ( page ) => {
                visitRoute( page, "users.show", { id : 5 } )
                assertRouteIs( page, "users.show" )
                expect( page ).toSee( "User 5" )
            } )
        } )
    }
}
  • From TestBox, through the annotation:
    • browse(), this.playwright(), browserAvailable(), ensureBrowserInstalled();
    • the browser matchers;
    • screenshots, traces and videos attached on failure;
    • the bundle browser, closed by the runner after the bundle.
  • BaseTestCase adds the named route helpers. They work on every engine, and only need the controller.
    • routeURL( name, params ) returns the path and query, built by ColdBox's own event.route().
    • visitRoute( page, name, params ) visits a named route.
    • assertRouteIs( page, name, params ) waits until the page is on that route. Without params it matches the route pattern, including optional placeholders. With params it matches the exact path.
  • Logged-in tests use bx-playwright saved sessions, with no test-only login endpoints in ColdBox:
    this.playwright().session( "admin", ( page ) => visitRoute( page, "login" ).fill( "Email", "admin@site.com" ).fill( "Password", "secret" ).click( "Sign in" ) )
    browse( ( page ) => visitRoute( page, "admin.dashboard" ), { session : "admin" } )
  • BaseModelTest, BaseInterceptorTest and any other spec can browse the same way.

Fixes

  • event.route( "name@module" ) built links with no slash after the module entry point (bloglogin/3/). A unit test in RequestContextTest covers it.
  • On Adobe, RequestContext.getMemento() included a this key, so RequestContextDecorator methods ran against the original context and missed its mocks. Regression specs in RequestContextDecoratorTest cover it.

Harness and tests

  • Test-harness routes:
    • users.show;
    • browserPosts, with an optional placeholder;
    • a plain login form (browserTesting.login) and browserTesting.whoami, for the saved-session specs.
  • tests/runner.cfm skips the BoxLang-only /browser folder on other engines.
  • BrowserTestingSpec (a BaseTestCase with @browser) covers:
    • route paths, for app and module routes;
    • assertRouteIs(), with and without optional placeholders;
    • a saved session created through the login form;
    • per-page session isolation.
  • The browser specs skip on TestBox releases without the annotation support. The route helper specs still run.

Verified locally, BoxLang 1.17.6, bx-playwright development + Chromium

  • tests.specs.browser: 15/15 pass with TestBox#222.
  • The same spec on the previous TestBox: 11 pass, 4 skip, 0 errors.
  • tests.specs.integration: 97/97 pass with the new BaseTestCase.

Type of change

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code (every new method has a docblock)
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works

🤖 Generated with Claude Code

https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi

claude added 4 commits October 1, 2026 11:58
event.route( "name@module" ) joined the module inherited entry point and the
route pattern without a separator, so "login@BrowserTesting" built
"__browser-testinglogin/3/". ModuleService stores inherited entry points
without a trailing slash, so the link now joins them with a single slash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
…dpoints

GET /__browser-testing/login/:id and GET /__browser-testing/logout call the
login and logout closures of moduleSettings.browserTesting. Every request
gets a plain 404 unless the environment is testing, the module is enabled,
a token is configured and matches the X-Browser-Testing-Token header or the
token variable (compared in constant time), and the closure is set. The
test harness enables it, and runs in its testing environment on 127.0.0.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
coldbox.system.testing.BrowserTestCase (BoxLang) extends BaseTestCase and
delegates to TestBox's BrowserSupport like BrowserSpec: browse(),
this.playwright(), browserAvailable(), the browserProfile and baseURL
annotations, the browser matchers and the afterAll closeBrowser() hook.
It adds routeURL(), visitRoute() and assertRouteIs() for named routes, and
loginAs() and logout() through the BrowserTesting core module.

The specs live in tests/specs/browser, which tests/runner.cfm skips on
engines other than BoxLang; the browser specs skip without bx-playwright.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Test Results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit 9870dff. ± Comparison against base commit ccc9834.

♻️ This comment has been updated with latest results.

…corator mocks on Adobe

BrowserTestCase registered testbox.system.browser.BrowserMatchers in its
pseudo constructor, so every runner.cfm request on BoxLang failed with a 500
on released TestBox builds that do not ship testbox.system.browser yet. It
now detects TestBox browser support, registers the matchers only when present,
and skips browser specs with a clear reason otherwise. The route helpers still
run.

RequestContext.getMemento() returned the `this` reference that Adobe keeps in
the variables scope. RequestContextDecorator restored it, so on Adobe the
decorator's inherited methods ran with `this` pointing to the original context
and mocked methods failed with "Element _MOCKRESULTS is undefined in THIS"
(BrowserTestingModuleSpec header token spec). The memento now leaves it out,
with regression specs in RequestContextDecoratorTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi

lmajano commented Oct 1, 2026 •

Copy link
Copy Markdown
Member Author

CI status on 32e827b: the remaining red jobs are not caused by this PR.

These need a re-run of the failed jobs (runs 36863474898 and 36863469751). I don't have permission to re-run them (403), so a maintainer needs to. I'll keep watching the PR.


Generated by Claude Code

@lmajano lmajano changed the title Browser testing for ColdBox apps: BrowserTestCase, named route helpers and test-only loginAs() COLDBOX-1457 Browser testing for ColdBox apps: BrowserTestCase, named route helpers and test-only loginAs() Oct 1, 2026
claude added 2 commits October 1, 2026 15:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
The test-only login endpoints were a backdoor. Logged-in browser tests now
use bx-playwright saved sessions: log in through the login page once with
this.playwright().session(), then browse( ..., { session : "name" } ).

The test harness gets a plain login form for the saved session specs, and
goes back to its development environment on 127.0.0.1. Module route specs
use the resourcesTest module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
@lmajano lmajano changed the title COLDBOX-1457 Browser testing for ColdBox apps: BrowserTestCase, named route helpers and test-only loginAs() COLDBOX-1457 Browser testing for ColdBox apps: BrowserTestCase and named route helpers Oct 1, 2026
@lmajano
lmajano requested a balanced review from Copilot October 1, 2026 20:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Route helpers incorrectly skip without browser support and fail to match valid optional route variants.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds BoxLang browser testing support to ColdBox and fixes request-context routing/decorator regressions.

Changes:

  • Introduces BrowserTestCase with browser and named-route helpers.
  • Adds browser harness routes, fixtures, and integration coverage.
  • Fixes module route joining and decorator memento handling.
File Description
AGENTS.md Documents BoxLang annotation and optional-value conventions.
changelog.md Records the feature and fixes.
system/​testing/​BrowserTestCase.bx Implements browser and route-testing helpers.
system/​web/​context/​RequestContext.cfc Fixes module links and memento copying.
test-harness/​config/​Router.cfc Registers browser-test routes.
test-harness/​handlers/​browserTesting.cfc Provides browser-test pages and login flow.
tests/​resources/​browser/​FakePage.bx Adds a browser-independent page test double.
tests/​runner.cfm Excludes BoxLang browser specs on other engines.
tests/​specs/​browser/​BrowserTestCaseSpec.bx Covers browser and route helpers.
tests/​specs/​web/​context/​RequestContextDecoratorTest.cfc Tests decorator scope isolation.
tests/​specs/​web/​context/​RequestContextTest.cfc Tests module route slash handling.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread system/testing/BrowserTestCase.bx Outdated
Comment thread system/testing/BrowserTestCase.bx Outdated
- visitRoute() no longer skips the spec without browser support: it only
  visits the page it gets, and pages come from browse(), which skips.
- assertRouteIs() matches every route registered with the name, so a route
  with optional placeholders (/posts/:id?, registered as /posts/:id and
  /posts) matches with and without them. Module routes use the module
  routing table.
- The harness login form renders the logged-in page instead of relocating:
  the harness runs with the test controller, which intercepts relocations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
lmajano and others added 2 commits October 8, 2026 19:59
…port

When closeBrowser() never runs (an afterAll() that throws, an aborted
request), TestBox's BrowserSupport closes the browser the next time a run
opens one (Ortus-Solutions/TestBox#212).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi

lmajano commented Oct 8, 2026 •

Copy link
Copy Markdown
Member Author

tests / Test Suites (boxlang-cfml@be, 21, true, 8) failed before any test ran, and the cause is not in this PR. The boxlang-cfml@be server failed to start because CommandBox could not resolve the engine:

No satisfying version found for [be]. ForgeBox can't find the package and you don't have a usable version in your local artifacts cache.

This is a ForgeBox/engine availability problem; the latest push (7146283) only changes a docblock. Its "TestBox Report boxlang-cfml@be-21" check passed only because no results were produced.

I could not re-run the job, because this integration has no permission to re-run workflows here (403). A maintainer can use "Re-run failed jobs" on run 37844858097 once the be engine resolves again.

The Adobe 2023 and 2025 jobs fail on one spec, ModuleServiceTest > Module Lifecycle ("test-module-conventions is not loaded"). This PR doesn't touch it, and it already fails the same way after the development merge.


Generated by Claude Code

Comment thread system/testing/BrowserTestCase.bx Outdated
* browse() and visitRoute() skip the running spec.
* Browser specs are not thread safe: do not use `asyncAll` in suites that browse.
*/
class extends="coldbox.system.testing.BaseTestCase" {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this extend from the TestBox BrowserSpec instead? I know we need the testbox stuff, but we need all the machinery in the new TestBox BrowserSpec?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't extend both. BaseTestCase extends testbox.system.compat.framework.TestCase and BrowserSpec extends BaseSpec, so it's single inheritance either way.

  • Extend BaseTestCase (current): we keep the ColdBox app loading that routeURL(), visitRoute() and assertRouteIs() rely on, plus execute(), getRequestContext() and the mapping annotations.
  • Extend BrowserSpec: we'd have to copy all of that ColdBox loading code instead.

It already gets all of BrowserSpec's machinery. BrowserSpec is only the matcher registration plus five one-line methods that delegate to testbox.system.browser.BrowserSupport:

Method What it does BrowserTestCase
browse() delegates to BrowserSupport same delegation
playwright() delegates to BrowserSupport same delegation
browserAvailable() delegates to BrowserSupport same delegation
getBrowserSupport() reads browserProfile and baseURL same
closeBrowser() marked @afterAll same

So it shares the bundle browser, the run-ledger cleanup, the failure attachments and the inline report screenshots. It adds the route helpers, plus a guard that skips browser specs on TestBox releases older than 7.2, which have no browser support.

What's duplicated is about 60 lines of thin delegation. To remove it, TestBox could expose the browser methods as a mixin that any spec class can pull in, and both BrowserSpec and BrowserTestCase would use it. That means a TestBox change first and then a ColdBox follow-up, so I've left it out of this PR.


Generated by Claude Code

claude added 2 commits October 9, 2026 19:32
…Case

TestBox now attaches browser support to any spec annotated with @browser,
@browserprofile or @baseurl (Ortus-Solutions/TestBox#222), so ColdBox no
longer needs a parallel BrowserTestCase hierarchy. BaseTestCase keeps
loading the app and gains the named route helpers: routeURL(),
visitRoute() and assertRouteIs(). BrowserTestCase is removed and its spec
becomes BrowserTestingSpec, a BaseTestCase with @browser.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
@lmajano lmajano changed the title COLDBOX-1457 Browser testing for ColdBox apps: BrowserTestCase and named route helpers COLDBOX-1457 Browser testing for ColdBox apps: @browser on BaseTestCase and named route helpers Oct 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
lmajano pushed a commit to ortus-docs/coldbox-docs that referenced this pull request Oct 9, 2026
ColdBox/coldbox-platform#708 drops BrowserTestCase: TestBox now attaches
browser support to any spec annotated with @browser, @browserprofile or
@baseurl (Ortus-Solutions/TestBox#222), and BaseTestCase gains the route
helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi
Adobe ColdFusion cannot call a member function on a parenthesized
expression, so `( route.regexPattern ?: "" ).reReplace(...)` failed to
compile on Adobe 2023/2025. The helpers now call the reReplace() function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi

lmajano commented Oct 9, 2026 •

Copy link
Copy Markdown
Member Author

CI status on 9870dff:

  • Adobe compile error fixed. Earlier commits failed on Adobe 2023/2025 because ( route.regexPattern ?: "" ).reReplace(...) does not compile there. 9870dff uses reReplace() function calls. Adobe 2025 now runs 907 tests with 904 passing, and the integration specs pass locally on Adobe 2025 (97/97).
  • Remaining Adobe 2025 failure is not from this PR. It is ModuleServiceTest > Module Lifecycle ("test-module-conventions is not loaded"). This PR does not touch module loading, and the same check is intermittent on development. The re-run could not be triggered from here (no permission to re-run workflows), so a maintainer re-run of that job should clear it.
  • Format check: the route helper signatures are now formatted.
  • Earlier BoxLang failures were infra. ForgeBox returned a 408 for cbstreams during box install, before any test ran.

Generated by Claude Code

@lmajano
lmajano merged commit 8dbc946 into development Oct 10, 2026
27 of 28 checks passed
@lmajano
lmajano deleted the claude/bx-playwright-research-plan-ddfqpm branch October 10, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants