You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: web/sites/guides/src/content/docs/v4-0-0/testing/browser-tests.mdx
+7-15Lines changed: 7 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,7 +38,7 @@ wheels browser setup
38
38
39
39
The installer resolves seven JARs from Maven Central (Playwright client, driver, driver-bundle, transitive deps) and caches them under a per-project install directory. Subsequent runs skip the download when hashes match. Pass `--force` to re-download. Chromium is the only engine the browser DSL wires today — Firefox and WebKit are not yet selectable.
40
40
41
-
If Playwright isn't installed when a spec runs, `BrowserTest.beforeAll` catches the missing-JAR error, sets `this.browserTestSkipped = true`, and the suite stays green. That's the safety net for CI cold starts and fresh dev machines — but every `it`in a browser spec still needs to check the flag.
41
+
If Playwright isn't installed when a spec runs, `BrowserTest.beforeAll` catches the missing-JAR error, sets `this.browserTestSkipped = true`, and the suite stays green. That's the safety net for CI cold starts and fresh dev machines. Inside `browserDescribe()` blocks the skip is automatic — its `aroundEach` returns early when the flag is set, so specs don't need a hand-written guard. Only `it`blocks inside a plain `describe()` need to check `this.browserTestSkipped` themselves.
1.**`extends="wheels.wheelstest.BrowserTest"`** — not `wheels.WheelsTest`. The browser base class adds the Playwright lifecycle hooks on top of BDD.
66
-
2.**`if (this.browserTestSkipped) return;`** — the first line of every `it`. When Playwright JARs aren't installed, or when the CI gate (`WHEELS_CI=true` without `WHEELS_BROWSER_CI_ENABLE=true`) is active, `beforeAll` sets the flag. Without the guard, the spec would crash on the first `this.browser` call instead of staying green.
65
+
2.**Use `browserDescribe()`, not plain `describe()`.** When Playwright JARs aren't installed, or when the CI gate (`WHEELS_CI=true` without `WHEELS_BROWSER_CI_ENABLE=true`) is active, `beforeAll` sets `this.browserTestSkipped` — and `browserDescribe`'s `aroundEach` skips each `it` automatically. If you put browser `it` blocks inside a plain `describe()` instead, you must guard them yourself with `if (this.browserTestSkipped) return;` or they crash on the first `this.browser` call.
`loginAs(identifier)` takes a single string (email, username, or whatever your app uses to identify a user) and navigates to `/_browser/login-as?identifier=...`. That route is mounted automatically in test mode. The default handler writes `session.userId = 1` and `session.userEmail = identifier` — enough for simple apps. If your app stores a richer session shape (e.g. `session.member = { id, email, firstName, lastName }`), add one line to `config/settings.cfm`:
174
+
`loginAs(identifier)` takes a single string (email, username, or whatever your app uses to identify a user) and navigates to `/_browser/login-as?identifier=...`. In your own app that route is mounted when `set(loadBrowserTestFixtures=true)` is on (default `false`) and the environment is `testing` or `development`. The default handler writes `session.userId = 1` and `session.userEmail = identifier` — enough for simple apps. If your app stores a richer session shape (e.g. `session.member = { id, email, firstName, lastName }`), add one line to `config/settings.cfm`:
@@ -276,14 +269,13 @@ These return a value instead of `this`, so they end the chain.
276
269
277
270
## Targeting form fields with `data-auto-id`
278
271
279
-
Wheels form helpers emit two selector hooks on every field — a camelCase`id="postTitle"` for browser URL hashes, and an underscored `data-auto-id="post_title"` for test selectors. The underscored form is stable against rename refactors; use it in browser specs.
272
+
Wheels form helpers emit two selector hooks on every field — a dashed`id="post-title"` for DOM labels and CSS, and an underscored `data-auto-id="post_title"` for test selectors. The underscored form is stable against rename refactors; use it in browser specs.
@@ -304,10 +296,10 @@ See [View & Form Tests](/v4-0-0/testing/view-and-form-tests/) for the same `data
304
296
305
297
Most of the DSL is engine-agnostic — Playwright drives a real Chromium regardless of which CFML engine is running your app. The exceptions:
306
298
307
-
-**Dialogs** — `acceptDialog`, `dismissDialog`, `dialogMessage` use `createDynamicProxy` to register a listener on Playwright's Java-side `Dialog` interface. That API is Lucee-only. On Adobe CF and BoxLang, calls throw `Wheels.DialogSupportMissing`. Wrap dialog-driven specs in an engine check (`if (server.coldfusion.productname != "Lucee") return;`) if you run the suite across engines.
308
-
-**`loginAs`** — relies on the `/_browser/login-as` fixture route, which is mounted automatically in test mode from `vendor/wheels/tests/routes.cfm`. If your app clears or replaces the route table in another spec, `BrowserTest.beforeAll` re-includes the fixture routes so browser specs stay self-contained.
299
+
-**Dialogs** — `acceptDialog`, `dismissDialog`, `dialogMessage` use `createDynamicProxy` to register a listener on Playwright's Java-side `Dialog` interface. That API is Lucee-only. On Adobe CF and BoxLang, calls throw `Wheels.BrowserDialogNotSupported`. Wrap dialog-driven specs in an engine check (`if (server.coldfusion.productname != "Lucee") return;`) if you run the suite across engines.
300
+
-**`loginAs`** — relies on the `/_browser/login-as` fixture route. Nothing restores that route automatically: if another spec clears or replaces the route table (a `$clearRoutes()`-style spec), `loginAs` and every `/_browser/*` request will 404 until the routes reload. Make any spec that manipulates the route table restore it before browser specs run.
309
301
310
-
See the cross-engine notes in [`CLAUDE.md`](https://github.com/cfwheels/cfwheels/blob/develop/CLAUDE.md) for the full list of Lucee/Adobe/BoxLang divergences.
302
+
See the cross-engine notes in [`CLAUDE.md`](https://github.com/wheels-dev/wheels/blob/develop/CLAUDE.md) for the full list of Lucee/Adobe/BoxLang divergences.
@@ -71,16 +71,17 @@ If you want the raw CLI instead of the bash wrapper, the equivalent is `wheels s
71
71
72
72
## Reporter output for CI
73
73
74
-
The `wheels test` command accepts a `--reporter=<name>` flag, but understanding what it does today avoids a surprise: the LuCLI implementation always requests JSON from the test endpoint and then prints a human-readable summary on success or failure details on failure. The flag is parsed but does not currently select between output formats — you get the same output regardless of `--reporter` value.
74
+
The `wheels test` command accepts a `--reporter=<name>` flag. The CLI always requests JSON from the test endpoint, then formats the result for the reporter you picked:
75
75
76
76
| Flag | Behaviour today |
77
77
| --- | --- |
78
78
| `--reporter=simple` (default) | Human-readable summary: `N passed (Xs)`on green, plus failure details on red |
79
-
| `--reporter=<anything else>` | Accepted, currently no-op — same human-readable output |
80
-
| `--ci` | Accepted, reserved for CI-specific behaviour — currently a no-op in the output path |
79
+
| `--reporter=json` | Emits the raw JSON result document — pipe it to `jq` or a post-processor |
80
+
| `--reporter=tap` | Emits TAP version 13 (`1..N`, `ok` / `not ok` lines) for TAP-consuming CI tooling |
81
+
| `--ci` | Accepted for forward-compatibility — currently changes nothing; every run already exits non-zero on failure |
81
82
| `--verbose` / `-v` | Adds the full bundle/suite/spec tree to the output |
82
83
83
-
For machine-readable results, call the test runner URL directly and post-process the JSON. That is exactly what `tools/ci/run-tests.sh` does in this repo: it `curl`s `/wheels/core/tests?db=sqlite&format=json`, parses the totals in Python, and emits a JUnit XML file that `actions/upload-artifact` ingests for the GitHub summary.
84
+
For machine-readable results you can also call the test runner URL directly and post-process the JSON. That is exactly what `tools/ci/run-tests.sh` does in this repo: it `curl`s `/wheels/core/tests?db=sqlite&format=json`, parses the totals in Python, and emits a JUnit XML file that `actions/upload-artifact` ingests for the GitHub summary.
84
85
85
86
```bash title="tools/ci/run-tests.sh (excerpt)"
86
87
curl -s -o "$RESULT_FILE" --max-time 600 \
@@ -102,7 +103,7 @@ Browser specs are expensive to run in CI — they need the Playwright JARs (~370
102
103
- **`WHEELS_CI=true`** — mark the environment as CI
103
104
- **`WHEELS_BROWSER_CI_ENABLE=true`** (or `1` or `yes`) — opt browser specs in
104
105
105
-
If `WHEELS_CI` is set and `WHEELS_BROWSER_CI_ENABLE` is not one of `true,1,yes`, `BrowserTest.cfc` sets `this.browserTestSkipped = true` in `beforeAll`. Every `it` block in a browser spec that begins with `if (this.browserTestSkipped) return;` then exits without running. The suite stays green; the browser tests simply don't count.
106
+
If `WHEELS_CI` is set and `WHEELS_BROWSER_CI_ENABLE` is not one of `true,1,yes`, `BrowserTest.cfc` sets `this.browserTestSkipped = true` in `beforeAll`, and `browserDescribe`'s `aroundEach` skips every `it` automatically. The suite stays green; the browser tests simply don't count.
Copy file name to clipboardExpand all lines: web/sites/guides/src/content/docs/v4-0-0/testing/fixtures-and-test-data.mdx
+20-12Lines changed: 20 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,6 +31,10 @@ The trigger conditions live in `vendor/wheels/tests/runner.cfm`. The runner read
31
31
32
32
This is a significant departure from Rails or Laravel, where the framework wraps each test in a transaction and rolls back. Wheels sets `application.wheels.transactionMode = "none"` in `runner.cfm`, so writes during a test run persist across specs. The two mechanisms you have for isolation are: (1) make `populate.cfm` idempotent so re-running it always resets the world, and (2) wrap destructive specs in a manual `transaction { ... }` block that rolls back.
33
33
34
+
<Asidetype="caution">
35
+
The runner applies these settings (and path swaps like `modelPath`) to the **live**`application.wheels` of the server it runs against — they can leak into normal requests served during and after a test run. Track [#3025](https://github.com/wheels-dev/wheels/issues/3025) for the isolation work; until then, prefer a dedicated test server over running the suite against the instance you're browsing.
36
+
</Aside>
37
+
34
38
<Asidetype="caution">
35
39
`populate.cfm` does not run between specs. A `create()` in `specs/models/PostSpec.cfc` leaves a row visible to `specs/models/CommentSpec.cfc`. Design specs to tolerate shared state, or roll back writes explicitly.
36
40
</Aside>
@@ -44,13 +48,17 @@ The canonical pattern is DROP + CREATE + seed, in that order, with the drops in
44
48
// Runs once per test run. Triggered by runner.cfm whenever url.populate is
45
49
// truthy (default) or when the sentinel table is missing.
Two things to notice. First, every call goes through `application.wo.execute(...)` rather than a bare `execute(...)`. That's the scope gotcha (covered below) — Wheels' internal functions are not available as globals inside a plain `.cfm` include. Second, the schema uses SQLite-flavoured syntax (`AUTOINCREMENT`, `TEXT`) because SQLite is the inner-loop reference platform for Wheels 4.0 tests.
108
+
Two things to notice. First, every call goes through native `queryExecute(...)` rather than a bare `execute(...)` — there is no global `execute()` in a plain `.cfm` include, and none on `application.wo` either; `execute()` exists only inside migration CFCs. That's the scope gotcha (covered below). Second, the schema uses SQLite-flavoured syntax (`AUTOINCREMENT`, `TEXT`) because SQLite is the inner-loop reference platform for Wheels 4.0 tests.
101
109
102
110
## Per-spec isolation — the manual pattern
103
111
@@ -137,7 +145,7 @@ Sometimes you want a model that exists only during the test run — a stripped-d
@@ -185,10 +193,10 @@ Factories trade a few lines of setup for a shorter, more readable spec body. Whe
185
193
186
194
## The scope gotcha
187
195
188
-
Wheels' internal functions (`model()`, `$dbinfo`, `execute()`, and friends) are not available as bare globals inside plain `.cfm` files that are included from CFCs like `TestRunner.cfc`. If you write `execute("CREATE TABLE ...")` in `populate.cfm`, you'll get a "variable EXECUTE is undefined" error.
196
+
Wheels' internal functions (`model()`, `$dbinfo`, and friends) are not available as bare globals inside plain `.cfm` files that are included from CFCs like `TestRunner.cfc`. If you write `execute("CREATE TABLE ...")` in `populate.cfm`, you'll get a "No matching function [EXECUTE] found" error (Adobe words it as "variable EXECUTE is undefined").
189
197
190
198
<Asidetype="caution">
191
-
Call framework internals via `application.wo.` (the scope where Wheels stores its callable methods) or use native CFML tags. `application.wo.execute("CREATE TABLE ...")` works; so does `<cfdbinfo>`, `<cfquery>`, and `<cfdump>`. Same rule for `application.wo.model("Post")` if you need to seed through the ORM instead of raw SQL.
199
+
Call framework internals via `application.wo.` (the scope where Wheels stores its callable methods) or use native CFML. `application.wo.model("Post")` works if you need to seed through the ORM. For raw SQL there is no framework helper at all here — `execute()` exists only on migration CFCs (`wheels.migrator.Migration`), not on `application.wo` — so use native `queryExecute(...)` with an explicit `datasource`, or `<cfquery>`/`<cfdbinfo>`.
192
200
</Aside>
193
201
194
202
This is the most common first-time-authoring bug in `populate.cfm`. If your tests die at boot with "function not defined" and the stack trace points at your populate, it's this.
0 commit comments