Blog

Luis Majano

October 13, 2026

Spread the word


Share your thoughts

Ortus Solutions today announces the general availability of ColdBox 8.3, a feature release focused on real-browser testing and route composition. Annotate a BaseTestCase spec with @browser and drive a real browser against your running ColdBox application, with named route helpers that know your routes. And with the Playwright driver behind it, the same browser can render PDFs and screenshots of your ColdBox views. This release also brings named route middleware with registerMiddleware(), route metadata shared by a whole group(), integration tests that run route middleware like a real request, and thread-safe WireBox scope wiring.

ColdBox 8.3 ships alongside bx-playwright 1.0 and TestBox 7.2. Together they put a real browser inside the tests you already write.

โš ๏ธ One breaking change: implicit views are now off by default

Before the features, the one change that can affect your upgrade. coldbox.implicitViews now defaults to false, for security.

Handlers that relied on ColdBox resolving a view from the event name, without calling event.setView(), will no longer render that view. Implicit views let a view be reached through event resolution without an explicit declaration, which can expose views that were never meant to be directly reachable and can bypass access-control decisions made only in handlers. Leaving them off also avoids the extra view discovery work on each request.

To keep the old behavior, set it in config/ColdBox.bx or config/ColdBox.cfc:

coldbox.implicitViews = true

Better, select your views explicitly with event.setView(). If you do re-enable implicit views, review view access and authorization first.

Browser testing for ColdBox apps ๐ŸŒ

There is no separate browser test class. Annotate any coldbox.system.testing.BaseTestCase spec with @browser, @browserProfile or @baseURL and TestBox attaches its browser support: browse(), this.playwright(), browserAvailable() and the retrying browser matchers.

Your spec still loads your application like any integration test, so it knows your routes, while it drives a real browser against your running application. BaseTestCase adds the named route helpers routeURL(), visitRoute() and assertRouteIs(), module routes included.

@appMapping( "/root" )
@browser
@baseURL( "http://127.0.0.1:8080" )
class extends="coldbox.system.testing.BaseTestCase" {

	function run() {
		describe( "Users", () => {
			it( "shows a user", () => {
				browse( ( page ) => {
					visitRoute( page, "users.show", { id : 5 } )
					assertRouteIs( page, "users.show", { id : 5 } )
					expect( page ).toSee( "User 5" )
				} )
			} )
		} )
	}

}

Named routes instead of hardcoded URLs ๐Ÿงญ

Hardcoded URLs make browser tests brittle. Name your routes in the router with as(), and routeURL() builds the path, visitRoute() opens it, and assertRouteIs() waits for the page to get there, with or without params. A page on the wrong route tells you the route it expected and the path it found. Module routes work too, as name@module or module:name. Rename a route pattern and your tests keep working, because they target the route name.

Log in once with saved sessions ๐Ÿ”

Filling the login form in every spec is slow. With bx-playwright saved sessions, you log in once through your real login page, and any spec can start already logged in. No test-only endpoints and no backdoors. Without a saved session, the page is a guest and lands on the login page, exactly like a real visitor. Sessions live outside your project, and you can refresh them with maxAge or refresh.

See the Browser Testing guide, including Named Routes and Authentication.

BoxLang exclusive. Browser testing requires BoxLang, the bx-playwright module and TestBox 7.2.0 or later.

It is not only for tests: PDFs, screenshots and rendering ๐Ÿ“„

The same Playwright driver that clicks through your app can also render it. That means real PDFs and images from a real browser engine, straight from your ColdBox application, with modern CSS, web fonts and everything your users see on screen.

Render a PDF from a template with the bx:playwrightRender component, for example in a ColdBox view:

<bx:playwrightRender type="pdf" path="invoice.pdf" format="A4" margin="1cm">
	<h1>Invoice ###prc.invoice.id###</h1>
</bx:playwrightRender>

Capture a screenshot of a route, in a spec, right after you assert on it:

browse( ( page ) => {
	visitRoute( page, "users.show", { id : 5 } )
	expect( page ).toSee( "User 5" )
	page.screenshot( "user-5.png" )
} )

One module gives your ColdBox app end-to-end tests, invoices, reports, social images and visual regression checks. Dig into the bx-playwright rendering docs and the release post.

Named route middleware: registerMiddleware() ๐Ÿท๏ธ

Router.registerMiddleware() registers a closure, lambda, object instance or WireBox ID under a name, so routes and groups reference it by name instead of repeating the target. A registered name works everywhere a middlewareGroup() name does: .middleware(), a group's middleware option and .withoutMiddleware().

function configure() {
	registerMiddleware( "onlyJson", ( event, rc, prc ) -> {
		if ( !event.isAjax() ) {
			event.renderData( type = "json", data = { "error" : "JSON only" }, statusCode = 406 ).noExecution()
		}
	} )

	group( { pattern : "/api", middleware : [ "onlyJson" ] }, () => {
		route( "/users" ).toHandler( "users" )
		route( "/health" ).withoutMiddleware( "onlyJson" ).toHandler( "health" )
	} )
}

Pass a struct of name : target pairs to register several at once. Registering a name that already exists, including a middlewareGroup() name, throws Router.DuplicateMiddleware unless you pass force = true. Register names before the routes that reference them.

This release also fixes passing a component instance directly to .middleware() on engines where isStruct() is true for components.

Group route metadata: meta ๐Ÿงฉ

group() now accepts a meta struct that every route inside the group inherits. Nested groups merge outer-first, and a route's own .meta() wins on conflict. One group can declare the metadata your middleware or security rules consume.

group( { pattern : "/admin", meta : { permissions : "ADMIN" } }, () => {
	route( "/users" ).to( "admin.users" )
	route( "/reports" ).meta( { permissions : "REPORTS" } ).to( "admin.reports" )
} )

// In a handler or middleware
var perms = event.getCurrentRouteMeta().permissions

The /users route inherits ADMIN. The /reports route overrides it with REPORTS.

Integration tests now run route middleware ๐Ÿงช

BaseTestCase.execute(), and the get(), post() and other HTTP helpers built on it, now run route-scoped middleware registered with .middleware(), in the same order as a real request: after the global preProcess announcement and before postProcess. A route protected by middleware is now protected in your integration tests too.

it( "blocks anonymous users from the admin", () => {
	var event = get( "/admin" )
	expect( event.getRenderData().statusCode ).toBe( 403 )
} )

If you have integration tests that hit routes with middleware attached, they now see that middleware. A test that expected to reach a protected handler without satisfying its middleware will now be blocked, exactly like a real request.

Under the hood: concurrency and scheduler fixes ๐Ÿ”’

  • Thread-safe WireBox scope wiring. Singleton, engine (application, session, server) and CacheBox scopes store an object before wiring it so circular dependencies resolve. Other threads could read that object before its dependencies were injected. A single wiring lock per injector now makes other threads wait until wiring completes, and circular partners stay marked as wiring until the outermost build finishes.
  • Scheduler fix. Calendar tasks such as everyDayAt(), everyHourAt() and everyMonthOn() combined with onOneServer() no longer drift to the scheduler's restart time after a restart. They keep their configured wall-clock time.

More fixes

  • Bootstrap.onSessionStart() no longer runs the session start handler on a controller that is still loading during a reinit.
  • Fixed an interceptor buffer pool race between a request and its async announce() thread.
  • RestHandler.onEntityNotFoundException() no longer throws MissingArgumentException on Adobe ColdFusion 2023.
  • event.route( "name@module" ) now builds module route links with the slash between the module entry point and the route pattern.
  • Adobe ColdFusion: request context decorators no longer copy the this reference of the original context.

Get started

Requirements for browser testing: BoxLang, the bx-playwright module and TestBox 7.2.0 or later.

install-bx-module bx-playwright
bxplaywright install
bxplaywright doctor

Then annotate a BaseTestCase spec with @browser and call browse(). Read the full What's New with 8.3.0 and the Browser Testing guide.

Resources

Need help, or want to support ColdBox?

Test your app like a user. Render it like a designer. Ship it like ColdBox.

Add Your Comment

Recent Entries

ACF2021 extended support ends on November 10. Is Your Application Ready?

ACF2021 extended support ends on November 10. Is Your Application Ready?

Adobe ColdFusion 2021 reaches the end of extended support on November 10, 2026. If your application still runs on this release, the time to decide what comes next is now.

Adobe ended core support for ColdFusion 2021 on November 10, 2025. The current extended support period is best effort and does not include security patches or hotfixes. When it ends on November 10 2026, organizations will no longer have that support option from Adobe for ColdFusion 2021.

What ...

Cristobal Escobar
Cristobal Escobar
October 13, 2026
TestBox 7.2 Real Browser Testing, Smarter Reports, and Agent-Ready Results

TestBox 7.2 Real Browser Testing, Smarter Reports, and Agent-Ready Results

Ortus Solutions today announces the general availability of TestBox 7.2, the biggest TestBox release in years. Built on the new bx-playwright 1.0 module, BoxLang specs can now drive a real browser, assert with web-first matchers that wait until they pass, and keep the screenshots, traces and videos of every failure. Around it, TestBox gains spec retries, a Run Failed option, a rebuilt set of HTML reporters with Ask AI on every failure, and a new Agent reporter that lets AI agents read a test run at minimal token cost.

Luis Majano
Luis Majano
October 13, 2026