I'm writing a System test to confirm the entire sign up flow is working in a Rails 7 app (with the Clearance gem and an email confirmation SignInGuard).
The test is working fine right up until I "click" the confirm link in the email (after parsing it with Nokogiri). For some reason the URL in the email points to my dev server (port 3000) instead of pointing to the test server (port 49736, 49757, 49991, whatever).
I could look up the current port the test server is using (it changes every run) and replace the port portion of the URL but that seems quite hacky. Am I missing something obvious or doing something wrong?
URL in mailer: confirm_email_url(@user.email_confirmation_token)
Route from rails routes
:
Prefix Verb URI Pattern Controller#Action
confirm_email GET /confirm_email/:token(.:format) email_confirmations#update
The system test so far:
require "application_system_test_case"
require "test_helper"
require "action_mailer/test_helper"
class UserSignUpFlowTest < ApplicationSystemTestCase
include ActionMailer::TestHelper
test "Sign up for a user account" do
time = Time.now
email = "test_user_#{time.to_s(:number)}@example.com"
password = "Password#{time.to_s(:number)}!"
# Sign up (sends confirmation email)
visit sign_up_url
fill_in "Email", with: email
fill_in "Password", with: password
assert_emails 1 do
click_on "Sign up"
sleep 1 # Not sure why this is required... Hotwire/Turbo glitch?
end
assert_selector "span", text: I18n.t("flashes.confirmation_pending")
# Confirm
last_email = ActionMailer::Base.deliveries.last
parsed_email = Nokogiri::HTML(last_email.body.decoded)
target_link = parsed_email.at("a:contains('#{I18n.t("clearance_mailer.confirm_email.link_text")}')")
visit target_link["href"]
# ^^^^^^^^^^^^^^^^^^^^^^^^^
# This is the bit that fails... The link in the email points to my dev server (port 3000) rather than
# the test server (port 49736, 49757, 49991, etc). I only figured this out when my dev server crashed
# and Selenium started choking on a "net::ERR_CONNECTION_REFUSED" error
assert_selector "span", text: I18n.t("flashes.email_confirmed")
end
end
For the time being I've worked around it by replacing visit target_link["href"]
with visit hacky_way_to_fix_incorrect_port(target_link["href"])
:
private
def hacky_way_to_fix_incorrect_port(url)
uri = URI(url)
return "#{root_url}#{uri.path}"
end
The URL used in mailers is specified by:
Rails.application.configure.action_mailer.default_url_options
In config/environments/test.rb
I had set mine to port 3000 when I first installed Clearance:
config.action_mailer.default_url_options = {host: "localhost:3000"}
To fix it, I first tried specifying the port dynamically but the suggested method didn't actually work and it seems it isn't necessary. Removing the port number was enough to get my system test passing:
config.action_mailer.default_url_options = {host: "localhost"}
As mentioned by Thomas Walpole, the reason this works is that Capybara.always_include_port
is set to true. With this setting, attempts to access http://localhost/confirm_email/<token>
(with no port specified) are automatically rerouted to http://localhost:<port>/confirm_email/<token>
.
The always_include_port
setting defaults to false in the Capybara gem but it turns out Rails sets it to true when it starts up the System Test server.