Docker环境下Cucumber+Devise+远程Selenium Chrome登录失败问题
背景
用rails-new在devcontainer搭建新系统,开发环境基于Docker。测试采用Docker化的Selenium Chromium,认证框架用Devise,BDD工具为Cucumber。
问题现象
手动登录功能正常,但执行Cucumber测试时登录失败。已确认测试中用户创建正确,first_user.valid_password?(password)返回true,但Devise返回401未授权。日志显示邮箱和密码参数传递正确,推测问题与CSRF令牌或会话相关。
相关配置代码
1. features/support/config.rb
require "capybara/cucumber" require "selenium-webdriver" Capybara.register_driver :selenium_remote_chrome do |app| options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--window-size=1400,1400") options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") Capybara::Selenium::Driver.new( app, browser: :remote, url: "http://#{ENV.fetch("SELENIUM_HOST")}:4444/wd/hub", options: options ) end Capybara.configure do |config| config.run_server = true config.server_port = ENV.fetch("CAPYBARA_SERVER_PORT", 3015).to_i config.server_host = "0.0.0.0" config.default_driver = :selenium_remote_chrome config.app_host = "http://rails-app:#{config.server_port}" end
2. Feature文件
Background: Given a user exists with email "user@example.com" and password "password123" Rule: A user can login with the correct email and password Scenario: User logs in successfully When I go to the login page And I fill in "Email" with "user@example.com" And I fill in "Password" with "password123" And I press "Log in" And I wait for the page to load Then I should see "Dashboard" And I should see "Log out"
3. 测试步骤代码
Given("a user exists with email {string} and password {string}") do |email, password| User.create!(email: email, password: password, password_confirmation: password) expect(User.count).to eq(1) # Ensure that the user was created u = User.first expect(u.valid_password?(password)).to be true # Ensure that the password is correct end When("I go to the login page") do visit new_user_session_path end When("I fill in {string} with {string}") do |field, value| fill_in field, with: value end When("I press {string}") do |button| click_button button end Then("I should see {string}") do |text| expect(page).to have_content(text) end And("I wait for the page to load") do sleep 1 end And("I wait for {int} seconds") do |seconds| sleep seconds end
日志信息
Completed 401 Unauthorized in 2ms (ActiveRecord: 0.2ms (1 query, 0 cached) | GC: 0.0ms) Processing by Devise::SessionsController#new as TURBO_STREAM Parameters: {"user"=>{"email"=>"user@example.com", "password"=>"password123"}, "commit"=>"Log in"} Rendering layout layouts/application.html.haml Rendering devise/sessions/new.html.haml within layouts/application Rendered devise/shared/_links.html.haml (Duration: 0.3ms | GC: 0.0ms) Rendered devise/sessions/new.html.haml within layouts/application (Duration: 2.5ms | GC: 0.0ms) Rendered layout layouts/application.html.haml (Duration: 3.3ms | GC: 0.0ms)
注:已移除日志中的邮箱和密码过滤,可确认参数传入正确。手动登录正常,求问问题原因及解决方案?
问题原因及解决方案
原因1:CSRF令牌验证不通过
Docker环境中,Selenium容器与Rails容器的主机名/域名不一致,导致浏览器存储的CSRF令牌对应的Origin与Rails服务器接收的请求Origin不匹配,Devise验证CSRF失败。
解决方法:
修改config/environments/test.rb,添加允许的主机:
Rails.application.configure do # 其他配置... config.hosts << "rails-app" # 对应Capybara的app_host中的主机名 end
原因2:Turbo Stream请求的CSRF处理问题
日志显示请求为as TURBO_STREAM,若登录表单未正确包含Turbo所需的CSRF元标签,或Capybara未正确处理Turbo请求方式,会导致CSRF验证失败。
解决方法:
- 确认登录页面布局包含Turbo的CSRF元标签(Rails 7+默认已包含):
# 在layouts/application.html.haml中 %meta{ name: "csrf-token", content: form_authenticity_token }
- 或在测试中禁用Turbo提交,修改点击按钮的步骤:
When("I press {string}") do |button| find_button(button).set_attribute("data-turbo", "false") click_button button end
原因3:会话存储跨容器配置问题
Docker容器间使用Cookie存储会话时,若Cookie的domain设置不正确,会导致会话无法共享。
解决方法:
修改config/environments/test.rb中的会话配置:
Rails.application.configure do # 其他配置... config.session_store :cookie_store, key: '_your_app_session', domain: :all end
原因4:容器网络解析问题
Selenium容器无法正确解析Rails容器的主机名rails-app,导致请求无法正常携带会话信息。
解决方法:
检查Docker Compose网络配置,确保Selenium容器与Rails容器在同一网络中;在Selenium容器中执行ping rails-app验证解析是否正常,若失败需调整网络别名配置。
内容的提问来源于stack exchange,提问作者John Small

