Full Stack Watch Mode
Last time I argued that system tests didn't fail — the tooling did. Capybara-style tests running in jsdom at 75ms proved the concept was sound. But 75ms per test, with a jsdom startup cost, still meant running the full suite was something you did deliberately — perhaps after every commit, not after every save.
That changed. The full suite now can run on every save.
One Codebase, Three Environments
In a typical JavaScript project, tests are split across environments: jsdom for unit tests, a browser runner for component tests, Playwright for end-to-end. The split exists because the front-end and back-end are different codebases with different runtimes.
When the entire stack is one codebase — transpiled Ruby — that split disappears. The same test file works everywhere because the application itself is portable:
# All three in Node.js + jsdom
npx juntos test -d sqlite
# All three in a real browser + IndexedDB
npx juntos test -d dexie
# System tests with Playwright
npx juntos e2e
Model tests, controller tests, and system tests. Same Ruby source files. The database flag determines the runtime — -d sqlite runs in Node.js with jsdom, -d dexie launches Chromium with Vitest's browser mode. No configuration files to maintain, no conditional imports, no test-environment shims.
Four Tiers
One source file, four levels of confidence:
| Tier | Command | Runtime | When |
|---|---|---|---|
| Browser | juntos test -d dexie |
Vitest + Chromium | Development (every save) |
| jsdom | juntos test -d sqlite |
Vitest + jsdom | CI, no browser available |
| E2E | juntos e2e |
Playwright | Pre-deploy, visual regression |
| Human | — | Eyes and judgment | Feature dev |
Add --ui to any tier for a visual dashboard with a test tree, module graph, and source viewer which will show the original Ruby source code and transpiled JavaScript side by side.
47ms Per Test
Vitest's browser mode runs tests inside a real Chromium instance instead of jsdom. Same minitest and Capybara APIs, same test files, real DOM. Measuring per-test cost — duplicating test files to isolate execution time from startup overhead:
| Runner | Per-test cost |
|---|---|
| Vitest/browser | ~47ms |
| Vitest/jsdom | ~75ms |
| Playwright | ~250ms |
| Rails/Selenium | ~425ms |
Browser mode is faster per-test than jsdom. That's counterintuitive until you consider what jsdom actually is — a JavaScript reimplementation of the browser DOM. A real browser has a compiled, optimized DOM engine. jsdom is simulating one in the same language the tests are written in. The native engine wins.
This changes the calculus. In the previous post, jsdom's speed was the tradeoff for lower fidelity. Now the browser tier is both faster and more faithful.
Continuous Testing
But the real shift isn't the speed alone — it's that browser mode defaults to watch. Tests re-run automatically on every file change. You don't decide to run the suite; it's always running. The only advantage jsdom retains is startup time — no browser to launch — which matters in CI but not in development.
Start juntos test. The browser stays open. Edit a model, a controller, or a test file, and the affected portions of the test suite — model tests, controller tests, system tests — re-run automatically. You see failures before you've switched windows.
Not a subset. Not just unit tests. The Capybara-style tests that visit, click_on, fill_in, and assert_text — the tests DHH gave up on because they were too slow and flaky — running on every save at 47ms each with zero flakiness.
Continuous integration runs tests after every commit. This is one step closer: tests after every save. The suite is fast enough that there's no reason not to.
Try It
Open the live editor. No install required.
The left pane is Monaco with the Ruby source. The right pane is the Vitest browser UI showing 21 passing tests. Edit a model, break a validation, and watch a test fail. Fix it, watch it pass. The whole development loop — code, test, fix — in one browser tab, running on WebContainers.
When you are ready, install locally:
npx github:ruby2js/juntos --demo blog
cd blog
npx juntos test -d dexie --ui
Under the Hood
The live editor boots a WebContainer, installs packages from tarballs, and runs juntos test with the preview provider — a lightweight browser test runner that needs no Playwright binary. When Vitest tries to open a browser, the WebContainer fires an xdg-open event that the editor intercepts to display the test page in an iframe. Same test infrastructure, just with the browser being the one you're already in.
Juntos is open source: github.com/ruby2js/ruby2js