Repository navigation
COLDBOX-1457 Browser testing for ColdBox apps: @browser on BaseTestCase and named route helpers - #708
Conversation
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
…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
|
CI status on
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 |
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
There was a problem hiding this comment.
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
Open (2)
What changed in this PR
Adds BoxLang browser testing support to ColdBox and fixes request-context routing/decorator regressions.
Changes:
- Introduces
BrowserTestCasewith 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.
- 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
…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
|
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 The Adobe 2023 and 2025 jobs fail on one spec, Generated by Claude Code |
| * 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" { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 thatrouteURL(),visitRoute()andassertRouteIs()rely on, plusexecute(),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
…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
…@browser support 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
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
|
CI status on 9870dff:
Generated by Claude Code |

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: annotateBaseTestCaseinstead (BoxLang)TestBox now attaches browser support to any spec whose class (or a parent class) has
@browser,@browserProfileor@baseURL(Ortus-Solutions/TestBox#222). So a ColdBox browser test is just aBaseTestCase:browse(),this.playwright(),browserAvailable(),ensureBrowserInstalled();BaseTestCaseadds 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 ownevent.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.BaseModelTest,BaseInterceptorTestand 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 inRequestContextTestcovers it.RequestContext.getMemento()included athiskey, soRequestContextDecoratormethods ran against the original context and missed its mocks. Regression specs inRequestContextDecoratorTestcover it.Harness and tests
users.show;browserPosts, with an optional placeholder;browserTesting.login) andbrowserTesting.whoami, for the saved-session specs.tests/runner.cfmskips the BoxLang-only/browserfolder on other engines.BrowserTestingSpec(aBaseTestCasewith@browser) covers:assertRouteIs(), with and without optional placeholders;Verified locally, BoxLang 1.17.6, bx-playwright
development+ Chromiumtests.specs.browser: 15/15 pass with TestBox#222.tests.specs.integration: 97/97 pass with the newBaseTestCase.Type of change
@browser)Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01UU9aLossoYUkQNwubnitBi