playwright-on-rails

This is a downstream copy of cypress-rails, adapted to support Playwright instead of Cypress. All credit goes to them. Changes here will be kept at a minimum, and suggestions should probably be sent to that project.

This is a simple gem to make it easier to start writing browser tests with Playwright for your Rails apps, regardless of whether your app is server-side rendered HTML, completely client-side JavaScript, or something in-between.

Installation

tl;dr:

  1. Install the npm package playwright
  2. Install this gem playwright-on-rails
  3. Run rake playwright:init

Installing Playwright itself

The first step is making sure Playwright is installed (that's up to you, this library doesn't install Playwright, it just provides a little Rails-specific glue).

If you're on newer versions of Rails and using webpacker for your front-end assets, then you're likely already using yarn to manage your JavaScript dependencies. If that's the case, you can add Playwright with:

$ yarn add --dev playwright

If you're not using yarn in conjunction with your Rails app, check out the Playwright docs on getting it installed. At the end of the day, this gem just needs the playwright binary to exist either in ./node_modules/.bin/playwright or on your PATH.

Installing the playwright-on-rails gem

Now, to install the playwright-on-rails gem, you'll want to add it to your development & test gem groups of your Gemfile, so that you have easy access to its rake tasks:

group :development, :test do
  gem "playwright-on-rails"
end

Once installed, you'll want to run:

$ rake playwright:init

This will override a few configurations in your playwright.config.js configuration file.

Usage

Develop tests interactively with playwright open

When writing tests with Playwright, you'll find the most pleasant experience (by way of a faster feedback loop and an interactive, easy-to-inspect test runner) using the playwright open command.

When using Rails, however, you'll also want your Rails test server to be running so that there's something for Playwright to interact with. playwright-on-rails provides a wrapper for running playwright open with a dedicated Rails test server.

So, by running:

$ rake playwright:open

Any JavaScript files added to playwright/tests will be identified by Playwright as tests. Simply click a test file in the Playwright application window to launch the test in a browser. Each time you save the test file, it will re-run itself.

Run tests headlessly with playwright run

To run your tests headlessly (e.g. when you're in CI), you'll want the run command:

$ rake playwright:run

Managing your test data

The tricky thing about browser tests is that they usually depend on some test data being available with which to exercise the app efficiently. Because playwright is a JavaScript-based tool and can't easily manipulate your Rails app directly, playwright-on-rails provides a number of hooks that you can use to manage your test data.

Here's what a config/initializers/playwright-on-rails.rb initializer might look like:

return unless Rails.env.test?

PlaywrightOnRails.hooks.before_server_start do
  # Called once, before either the transaction or the server is started
end

PlaywrightOnRails.hooks.after_transaction_start do
  # Called after the transaction is started (at launch and after each reset)
end

PlaywrightOnRails.hooks.after_state_reset do
  # Triggered after `/playwright_on_rails_reset_state` is called
end

PlaywrightOnRails.hooks.before_server_stop do
  # Called once, at_exit
end

(You can find an example initializer in this repo.)

The gem also provides a special route on the test server: /playwright_on_rails_reset_state. Each time it's called, playwright-on-rails will do two things at the beginning of the next request received by the Rails app:

  • If PLAYWRIGHT_RAILS_TRANSACTIONAL_SERVER is enabled, roll back the transaction, effectively resetting the application state to whatever it was at the start of the test run

  • Trigger any after_state_reset hooks you've configured (regardless of the transactional server setting)

This way, you can easily instruct the server to reset its test state from your Playwright tests like so:

beforeEach(() => {
  cy.request('/playwright_on_rails_reset_state')
})

(Remember, in Playwright, before is a before-all hook and beforeEach is run between each test case!)

Configuration

Environment variables

The playwright-on-rails gem is configured entirely via environment variables. If you find yourself repeating a number of verbose environment variables as you run your tests, consider invoking the gem from a custom script or setting your preferred environment variables project-wide using a tool like dotenv.

  • PLAYWRIGHT_RAILS_DIR (default: Dir.pwd) the directory of your Rails project
  • PLAYWRIGHT_RAILS_PLAYWRIGHT_DIR (default: same value as rails_dir) the directory of your Playwright project
  • PLAYWRIGHT_RAILS_HOST (default: "127.0.0.1") the hostname to bind to
  • PLAYWRIGHT_RAILS_PORT (default: a random available port) the port to run the Rails test server on
  • PLAYWRIGHT_RAILS_BASE_PATH (default: "/") the base path for all Playwright's requests to the app (e.g. via cy.visit()). If you've customized your baseUrl setting (e.g. in playwright.config.js), you'll need to duplicate it with this environment variable
  • PLAYWRIGHT_RAILS_TRANSACTIONAL_SERVER (default: true) when true, will start a transaction on all database connections before launching the server. In general this means anything done during playwright open or playwright run will be rolled back on exit (similar to running a Rails System test)
  • PLAYWRIGHT_RAILS_PLAYWRIGHT_OPTS (default: none) any options you want to forward to the Playwright CLI when running its open or run commands.

Example: Running a single spec from the command line

It's a little verbose, but an example of using the above options to run a single Playwright test would look like this:

$ PLAYWRIGHT_RAILS_PLAYWRIGHT_OPTS="playwright/integration/a_test.js" bin/rake playwright:run

Initializer hooks

before_server_start

Pass a block to PlaywrightOnRails.hooks.before_server_start to register a hook that will execute before the server or any transaction has been started. If you use Rails fixtures, it may make sense to load them here, so they don't need to be re-inserted for each request

after_server_start

Pass a block to PlaywrightOnRails.hooks.after_server_start to register a hook that will execute after the server has booted.

after_transaction_start

If there's any custom behavior or state management you want to do inside the transaction (so that it's also rolled back each time a reset is triggered), pass a block to PlaywrightOnRails.hooks.after_transaction_start.

after_state_reset

Every time the test server receives an HTTP request at /playwright_on_rails_reset_state, the transaction will be rolled back (if PLAYWRIGHT_RAILS_TRANSACTIONAL_SERVER is enabled) and the after_state_reset hook will be triggered. To set up the hook, pass a block to PlaywrightOnRails.hooks.after_state_reset.

before_server_stop

In case you've made any permanent changes to your test database that could pollute other test suites or scripts, you can use the before_server_stop to (assuming everything exits gracefully) clean things up and restore the state of your test database. To set up the hook, pass a block to PlaywrightOnRails.hooks.before_server_stop.

Configuring Rails

Beyond the configuration options above, you'll probably also want to disable caching in your Rails app's config/environments/test.rb file, so that changes to your Ruby code are reflected in your tests while you work on them with rake playwright:open. (If either option is set to true, any changes to your Ruby code will require a server restart to be reflected as you work on your tests.)

To illustrate, here's what that might look like in config/environments/test.rb:

config.cache_classes = false
config.action_view.cache_template_loading = false

Setting up continuous integration

See original.

Why use this?

See original.