Rails - 失去与集成测试和 Capybara 的会话 - CSRF 相关?

Posted

技术标签:

【中文标题】Rails - 失去与集成测试和 Capybara 的会话 - CSRF 相关?【英文标题】:Rails - Losing session with Integration Tests and Capybara - CSRF related? 【发布时间】:2011-09-30 03:02:44 【问题描述】:

我正在使用 Rails 3.1.0.rc4,我正在使用 capybara 的新 Steak-like DSL 和 Rspec 进行集成测试(使用 Devise 身份验证)

我遇到的问题是,当我运行集成测试时,来自 capybara 的机架测试驱动程序似乎完全失去了用户的登录会话,实际上,会话似乎完全清除了。

经过几天的调试,我完全不知道为什么。逐行遍历中间件堆栈,我相信我已经将问题归结为导致此问题的 ActiveRecord::SessionStore 中发生的事情。我读过here,如果 Rails 无法验证 CSRF 令牌,它将清除会话,这让我相信我配置错误,并且由于某种原因,这个测试没有验证 CSRF令牌正确。

这是 /initializers 目录中 session_store.rb 中的内容:

MyApp::Application.config.session_store :active_record_store

是否有任何了解 Rails 中的 CSRF 保护的人对为什么会发生这种情况有任何线索?

另外,还有一些注意事项:

我要测试的东西实际上可以在浏览器本身中运行,只有这一项测试会丢弃会话 将操作 url 指向另一台服务器的表单提交到另一台服务器后,会话似乎被丢弃。我在测试中使用 VCR gem 来捕获对这个外部服务器的请求/响应,虽然我相信我已经裁定外部请求是问题,但这可能与 CSRF 令牌未进行身份验证直接有关,从而清除会话。 其他涉及登录/使用会话的测试不会丢弃会话

谁能告诉我这里到底发生了什么,以及为什么一个测试似乎随意放弃了它的会话并在我身上失败了?我做了很多调试,并尝试了我能想到的一切。

【问题讨论】:

我也有同样的问题...啊... 【参考方案1】:

我也是水豚新手,遇到了类似的问题。

我试图登录一个用户,做这样的事情:

post user_session_path, :user => :email => user.email, :password => 'superpassword'

在我尝试对 capybara 做一些事情之前,这一切正常,例如访问一个页面并测试用户是否已登录。这个简单的测试没有通过:

visit root_path
page.should have_content("logout") #if the user is logged in then the logout link should be present

起初我以为水豚正在清除会话,但我错了。我花了一些时间才意识到驱动程序 capybara 正在使用处理自己的会话,因此,从 capybara 的角度来看,我的用户从未登录过。为此,您必须这样做

page.driver.post user_session_path, :user => :email => user.email, :password => 'superpassword'

不确定这是否是您的情况,但希望对您有所帮助。

【讨论】:

谢谢,阿里拉!也为我工作。不错的侦探。 似乎在后来的水豚中停止了工作。我有类似 thsi 的东西,并且对 pre-1 版本不太满意。现在我升级到 2.1 它以这种方式失败。鉴于此答案的日期,它很可能自 capybara 1 以来一直以这种方式工作。很好的解释!【参考方案2】:

手动的方式很简单:

it "does something after login" do
  password = "secretpass"
  user = Factory(:user, :password => password)
  visit login_path
  fill_in "Email", :with => user.email
  fill_in "Password", :with => password
  click_button "Log in"
  visit # somewhere else and do something
end

然后您可以将其分解为“spec_helper.rb”中的一个函数:

# at the bottom of 'spec_helper.rb'
def make_user_and_login
  password = "secretpass"
  @user = Factory(:user, :password => password)
  visit login_path
  fill_in "Email", :with => @user.email
  fill_in "Password", :with => password
  click_button "Log in"
end

并在您的任何测试中使用它(可能要求规范):

it "does something after login" do
  make_user_and_login
  # now test something that requires a logged in user
  # you have access to the @user instance variable
end

【讨论】:

【参考方案3】:

这可能是一个很长的机会,但我相信在click_button 'Sign in' 之后我们会陷入糟糕的状态,并在之后立即调用visit elsewhere

我的理论是,当我们单击按钮时,请求尚未完成,我们通过访问另一条路径将其终止。

来自 Capybara 文档:

当向 DSL 发出指令时,例如:

click_link('foo') click_link('bar') expect(page).to have_content('baz')

如果单击 foo 链接会触发异步过程,例如 Ajax 请求,该过程完成后会将 bar 链接添加到页面,单击 bar 链接会失败,因为该链接不会还存在。 但是 Capybara 足够聪明,可以在短时间内重试查找链接,然后放弃并抛出错误。

如果是这种情况,解决方案很简单:给 Capybara 寻找的东西,让它等到请求完成。这可以像添加一样简单:

expect(page).to have_text('Signed in as bob@example.com')

【讨论】:

【参考方案4】:

我可以通过在 config/initializers/test.rb 中将此值设置为 true 来修复此错误

# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = true

之前,CSRF <meta> 标签没有打印到<head>。更改此值后,它们终于出现了。

【讨论】:

我相信 config/environments/test.rb 是预期的。

以上是关于Rails - 失去与集成测试和 Capybara 的会话 - CSRF 相关?的主要内容,如果未能解决你的问题,请参考以下文章

集成测试ActionMailer和ActiveJob

在 Rails 中使用 Capybara 和 Selenium 测试 Google 地图标记

如何在测试之间重复使用 Capybara 会话?

在 Rails 3.1 中使用 Capybara、Rspec 和 Selenium 进行测试时登录失败

Rails Cucumber使用Capybara测试AJAX

Rails + Cucumber/Capybara:如何在测试中设置/检索 cookie?