April 27, 2017·6 min read·engineeringrubyrailstestingjavascript

Rails 5.1 brings the browser into the framework's test model

Rails 5.1 integrates Capybara system tests and supports JavaScript dependencies through Yarn. The release also separates encrypted secrets from their master key and adds parameterized mailers.

A controller integration test can verify a response without running the JavaScript that turns that response into an application. Once important behavior depends on the browser, that gap limits what the test establishes. Rails 5.1 makes browser-driven system tests a framework facility, with configuration and failure handling supplied around Capybara.

The April 27 final-release announcement places this alongside support for Yarn and optional webpack compilation. These changes acknowledge the browser as part of the system a Rails application delivers. Rails can continue to organize the server-side application while integrating tools and tests for code that executes elsewhere.

I find Ruby's blocks and readable object-oriented code useful, but a readable controller does not establish the behavior of a JavaScript interaction. The system-test addition makes sense as a way to exercise that interaction through the framework. It also reduces the setup that previously stood between an application and a useful browser test.

Capybara gets a Rails configuration boundary

Rails does not invent a new browser interaction language. Its SystemTestCase implementation includes Capybara's DSL and Minitest assertions, along with setup, teardown and screenshot helpers. The announcement describes the work as a wrapper around Capybara, credited to Eileen M. Uchitelle's extraction from Basecamp.

A generated ApplicationSystemTestCase provides the application's configuration point. The default uses Selenium with Chrome and a 1400-by-1400 browser window. Tests inherit from that application class, so the application can change its browser configuration centrally instead of repeating it in each test.

require "test_helper"

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end

The source documentation shows ordinary Capybara operations such as visiting a route, clicking a link, filling in a field and asserting visible text. These operations describe the user-visible interaction while the browser executes the application's JavaScript. That covers a different part of the application than inspecting the controller's response body alone.

Failure screenshots add another kind of evidence. A failed browser assertion can depend on what was rendered at that point, and a screenshot preserves some of that state. It complements the assertion and error report. It does not replace a precise expectation about the behavior being tested.

The driver remains configurable. Selecting Firefox uses the using option, while headless drivers use the corresponding Capybara integrations. The documentation says browser and screen-size options do not apply to headless drivers and are silently ignored there. A configuration should therefore be interpreted according to the selected driver, rather than assuming every option affects every test backend.

Database cleanup is part of the integration

The release announcement says the framework's transactional tests now roll back system-test changes, removing the need for additional database-cleanup strategies for that integration. This addresses a practical barrier to combining a real browser with Rails' existing test setup. Browser execution should not require each application to rediscover the same database coordination.

The test class is a shim between Rails and Capybara. Its source starts the Rails application through a Rack builder and a system-testing server. The framework can therefore connect browser interactions to the application under test while supplying the lifecycle helpers around each case.

This integration does not make browser tests as cheap as isolated model tests. The announcement explicitly acknowledges the slower whole-browser setup. Their value comes from the additional behavior they exercise, including the JavaScript that ordinary controller integration tests do not run. Selecting the test level depends on which behavior needs evidence.

I value a test suite that can state the browser behavior directly without using it to prove every internal detail. A model invariant can have a focused test, while an interaction that depends on JavaScript needs a route through the browser. Rails 5.1 supplies the latter without removing the framework's other testing levels.

Yarn supplies JavaScript dependencies

Rails 5.1 integrates Yarn for dependencies from NPM. The announcement describes bin/yarn as the application entry point for adding them. Dependencies obtained this way become available to the asset pipeline, similar to vendored dependencies. A library no longer needs to arrive through a Ruby packaging convention merely because a Rails application consumes it.

Webpack compilation is optional through the Webpacker gem. New projects can request --webpack or a tailored configuration for React, Angular or Vue. The announcement explicitly keeps this compatible with the asset pipeline, including mixed use where some JavaScript uses webpack and other assets remain in the pipeline.

This is a division of responsibilities within one application. Yarn manages the JavaScript package dependencies, while webpack can handle compilation where selected. Images, fonts and other assets can continue through the established pipeline. Adopting one JavaScript integration does not require moving every asset through the same compiler.

Rails UJS also stops requiring jQuery. The rewritten rails-ujs uses vanilla JavaScript for behavior such as remote requests and confirmations. An application can still choose jQuery, but the framework no longer adds it solely to provide those UJS behaviors. That makes the dependency a choice of the application instead of a prerequisite of that integration.

These changes make system tests more relevant because a Rails application can now incorporate browser dependencies through an explicit package-management path. A successful server response cannot establish that those dependencies load and cooperate correctly in the browser. The integrated system-test setup gives the application a way to exercise that delivered behavior.

Encrypted secrets keep the key separate

Rails 5.1 adds encrypted secrets inspired by Ara T. Howard's sekrets gem. Running bin/rails secrets:setup creates an encrypted secrets arrangement and a master key. The encrypted content can enter version control while the key remains outside the repository.

Production decryption uses an injected key file or RAILS_MASTER_KEY in the environment. This changes what belongs in the repository, but it does not eliminate the need to store and deliver a secret. The master key remains the sensitive input that permits decryption. Committing that key with the encrypted content would defeat the intended separation.

The announcement contrasts this with treating environment variables as the entire storage solution. Even when the runtime receives secrets through an environment, something still has to store them before injection. Encrypted secrets provide a versionable artifact while leaving key distribution as a separate operational responsibility.

That separation is useful for reviewing configuration changes. A repository can record a revision of the encrypted settings alongside the application that uses it. Possession of repository content alone is not intended to provide decryption. The release's mechanism depends on preserving the boundary between the encrypted file and its master key.

Mailer parameters exist before callbacks run

Parameterized mailers address a separate mismatch between controllers and mailers. A controller's params are available before the action, so callbacks can use them to share setup. Traditional mailer method arguments are not available to callbacks in the same way. The new option supplies parameters before invoking a mailer action.

The announcement describes this as backward compatible. Applications can convert selected mailers that benefit from shared setup without rewriting all existing mailer calls. Default sender and reply headers can then use that earlier parameter context where appropriate.

Form and route declarations cover more cases

The release also unifies form_for and form_tag patterns through form_with. The announcement describes a common entry point that can infer details from a model record or accept manual configuration. A form tied to a model and a manually configured form no longer require separate root APIs merely because their configuration comes from different places.

Direct routes address programmatic URL decisions. They can use Ruby logic to select a destination according to supplied parameters, instead of requiring an application to distribute that decision across unrelated helper methods. Resolved routes customize polymorphic lookup for a model. The announcement demonstrates a comment linking to its parent message with an anchor for that comment, showing that the model passed to a helper need not imply a direct route to that model's own page.

These features affect different application declarations, but each gives an existing convention an explicit way to describe a more involved case. They also provide concrete browser behavior for a system test to exercise, such as the destination produced for a model link or the submitted form's result.

They arrive with the stated maintenance-policy transition, under which ordinary bug fixes move to 5.1.x while the announcement lists security coverage for older branches. Application owners therefore have both new integration facilities and an explicit maintenance context for deciding when to adopt this release.