thoughtbot / clearance

Rails authentication with email & password.
https://thoughtbot.com
MIT License
3.7k stars 456 forks source link

How do I write system tests with clearance? #1022

Open avk opened 1 month ago

avk commented 1 month ago

I'm struggling with adding clearance to my system tests.

class PiecesTest < ApplicationSystemTestCase
  setup do
    @writer = writers(:one)
    @piece = pieces(:one)
    @fields = {
      draft: t("activerecord.attributes.piece.draft"),
      prose: t("activerecord.attributes.piece.prose"),
      title: t("activerecord.attributes.piece.title"),
      word_count: t("activerecord.attributes.piece.word_count_suffix"),
    }
  end

  test "should add a new prose piece" do
    visit pieces_url(as: @writer)
    click_on t("pieces.actions.new"), match: :first

    check @fields[:prose]
    fill_in @fields[:word_count], with: 5000
    fill_in @fields[:title], with: "Prose Piece"
    fill_in @fields[:draft], with: 1
    click_on t("pieces.actions.submit")

    assert_text t("pieces.actions.created")
  end
end

The backdoor middleware, will only work for the visit call (the first line of the test). It doesn't work with redirects, like the one generated by the click in the second line of the test.

The controller test helpers are also not available if I require "clearance/test_unit".

What else can I try to get this to work?

avk commented 1 month ago

This feels hacky, but here's the first approach I got working across multiple system tests and navigation even within a test:

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

  def sign_in_as(writer)
    # why? ensure cookies can be set on first navigation
    visit root_url if page.driver.browser.current_url == "about:blank"

    # Selenium driver interface to auth cookie borrowed from 
    # https://github.com/nruth/show_me_the_cookies/blob/master/lib/show_me_the_cookies/adapters/selenium.rb
    page.driver.browser.manage.add_cookie(
      # How Clearance keeps track of authenticated users
      name: Clearance.configuration.cookie_name,
      value: writer.remember_token
    )
  end

  def sign_out
    # Selenium driver interface to auth cookie borrowed from 
    # https://github.com/nruth/show_me_the_cookies/blob/master/lib/show_me_the_cookies/adapters/selenium.rb
    page.driver.browser.manage.delete_cookie(
      # How Clearance keeps track of authenticated users
      Clearance.configuration.cookie_name
    )
  end
end

And if I call sign_in_as in setup or in a test, the authentication will persist for the whole test.

class PiecesTest < ApplicationSystemTestCase
  setup do
    @writer = writers(:one)
    sign_in_as @writer
    @fields = {
      draft: t("activerecord.attributes.piece.draft"),
      prose: t("activerecord.attributes.piece.prose"),
      title: t("activerecord.attributes.piece.title"),
      word_count: t("activerecord.attributes.piece.word_count_suffix"),
    }
  end

  test "should add a new prose piece" do
    visit pieces_url # redirects if not signed in
    click_on t("pieces.actions.new"), match: :first # navigates to new page

    check @fields[:prose]
    fill_in @fields[:word_count], with: 1000
    fill_in @fields[:title], with: "Prose Piece"
    fill_in @fields[:draft], with: 1
    click_on t("pieces.actions.submit") # navigates to new page

    assert_text t("pieces.actions.created")
  end
end

The above works with secure cookies, but doesn't work with signed cookies.

Clearance.configure do |config|
  # ...
  config.signed_cookie = false # breaks system tests
  # ...
end

This approach was considerably faster than navigating to sign_in_path and filling out the session form, and as stated originally, more flexible than the backdoor when navigating across pages without explicit visit calls.

I'm very open to other approaches.