Rails与子域集成测试,RSpec和capybara-webkit(适用于JavaScript)

时间:2016-08-20 19:00:03

标签: ruby-on-rails ruby rspec capybara capybara-webkit

我在使用capybara-webkit进行集成测试时遇到问题。该问题与使用子域直接相关。

我的所有正常(非JS)集成测试都在运行,但我无法弄清楚如何让它为多租户应用程序工作。 我正在使用公寓gem进行多租户,它使用PostgreSQL模式来分割用户数据。用户可以注册,选择子域,然后访问该应用程序。

应用程序设置如下:

  • routes.rb中,我有一些限制,具体取决于子域是否存在(查看是否request.subdomain.present?)。如果没有子域存在,则唯一可用的路由是resources :accounts, only: [:new, :create],否则其他应用程序路由可用。

我的accounts_controller上有一个before_action来调用私有方法load_schema切换到当前子域或重定向到root_url。

def load_schema
  Apartment::Tenant.switch!('public')
  return unless request.subdomain.present?

  if current_account
    Apartment::Tenant.switch!(current_account.subdomain)
  else
    redirect_to root_url(subdomain: false)
  end
end

def current_account
  @current_account ||= Account.find_by(subdomain: request.subdomain)
end
helper_method :current_account

在我的所有RSpec功能规范(当前正在传递的不使用JavaScript)中,我运行了sign_user_in方法来签署用户:

def sign_user_in(user, opts={})
  if opts[:subdomain]
    visit new_user_session_url(subdomain: opts[:subdomain])
  else
    visit new_user_session_path
  end

  fill_in 'user[email]', with: user.email
  fill_in 'user[password]', with: (opts[:password] || user.password)
  click_button 'Log in'
end

我的所有规格都通过了这种方法。

但是,当我使用:js on运行规范时,它永远无法找到'user [email]'。视图使用引导模式弹出创建或编辑操作,然后使用AJAX(通过remote:true)添加或更新页面上新创建的资源。我目前试图通过的一个简单的功能规范是:

require 'rails_helper'

feature 'user creates product' do
  let(:user) { build(:user) }
  let(:account) { create(:account_with_schema, owner: user) }

  scenario 'successfully', :js do
    product = build_stubbed(:product, name: 'Test Product', amazon_sku: 'test_sku', price: 9.99)
    sign_user_in(user, subdomain: account.subdomain)
    click_on 'Products'
    click_on 'New Product'
    fill_in 'product[name]', with: product.name
    fill_in 'product[amazon_sku]', with: product.amazon_sku
    fill_in 'product[price]', with: product.price
    click_on 'Create Product'

    expect(page).to have_content('Test Product')
  end
end

在我的日志中,它显示它正在尝试连接到subdomain1.example.com/users/sign_in:

Received "Visit(http://subdomain1.example.com/users/sign_in)" 
Started "Visit(http://subdomain1.example.com/users/sign_in)" 
Load started 
"Visit(http://subdomain1.example.com/users/sign_in)" started page load 
Started request to "http://subdomain1.example.com/users/sign_in" 
Finished "Visit(http://subdomain1.example.com/users/sign_in)" with response "Success()" 
Received 200 from "http://subdomain1.example.com/users/sign_in" 

此子域名1基于我对我的帐户工厂的序列,例如:

factory :account do
    sequence(:subdomain) { |n| "subdomain#{n}" }
...

当然这会失败,因为它无法连接到subdomain1.example.com:

Failures:

  1) user creates product successfully
     Failure/Error: fill_in 'user[email]', with: user.email

     Capybara::ElementNotFound:
       Unable to find field "user[email]"

当我在本地测试时,我使用lvh.me在本地测试子域(因为你不能在localhost上做子域)。

由于我看到了这些错误,因此我将以下内容添加到我的rails_helper.rb中:

Capybara::Webkit.configure do |config|
  config.debug = true
  config.allow_unknown_urls
  config.allow_url("lvh.me")
  config.allow_url("*.lvh.me")
end

但这仍然导致同样的事情。经过一些谷歌搜索,我发现一个问题,提到使用“路径”与“网址”。例如,在我的sign_user_in帮助

而不是:

  if opts[:subdomain]
    visit new_user_session_url(subdomain: opts[:subdomain])
  else

使用:

  if opts[:subdomain]
    visit new_user_session_path(subdomain: opts[:subdomain])
  else

当我这样做时,我似乎越来越近了,因为日志现在显示了这个:

Received "AllowUrl(*.lvh.me)" 
Started "AllowUrl(*.lvh.me)" 
Finished "AllowUrl(*.lvh.me)" with response "Success()" 
Wrote response true "" 
Received "Visit(http://lvh.me:3000/users/sign_in)" 
Started "Visit(http://lvh.me:3000/users/sign_in)" 

很酷,进步。虽然它不包括子域,即使它正在被传入。另外,这会导致其他5个测试因路由错误而全部失败:

例如:

  5) user authentication does not allow user from one subdomain to sign in on another subdomain
     Failure/Error: visit new_user_session_path(subdomain: opts[:subdomain])

     ActionController::RoutingError:
       No route matches [GET] "/users/sign_in"

即使传入了子域,它也会被忽略,除非有子域,否则该路由不可用。

经过一些谷歌搜索并弄清楚如何为lvh.me设置config.allow_url部分之后,我还发现我应该将它添加到我的development.rb文件中,以便知道端口:

Capybara.always_include_port = true

这是有效的,因为上面的日志输出显示它正在使用端口3000。

接下来,我更改了sign_user_in方法,再次使用访问new_user_session_url(子域名:opts [:subdomain])(因此我之前的规范仍然会通过)。按照这个SO线程的建议:Capybara with subdomains - default_host我对我的规范进行了调整:

before(:each) do
  set_host "lvh.me:3000"
end

def set_host (host)
  default_url_options[:host] = host
  Capybara.app_host = "http://" + host
end

现在,当我运行规范时,我在日志中看到以下内容:

Received "AllowUrl(*.lvh.me)" 
Started "AllowUrl(*.lvh.me)" 
Finished "AllowUrl(*.lvh.me)" with response "Success()" 
Wrote response true "" 
Received "Visit(http://subdomain1.lvh.me:3000/users/sign_in)" 
Started "Visit(http://subdomain1.lvh.me:3000/users/sign_in)" 
Load started 
"Visit(http://subdomain1.lvh.me:3000/users/sign_in)" started page load 
Started request to "http://subdomain1.lvh.me:3000/users/sign_in" 
Finished "Visit(http://subdomain1.lvh.me:3000/users/sign_in)" with response "Success()" 
Started request to "http://lvh.me:3000/" 
Received 302 from "http://subdomain1.lvh.me:3000/users/sign_in" 
Started request to "http://lvh.me:3000/assets/application.self-e7adbbd6d89b36b8d2524d4a3bbcb85ee152c7a2641271423c86da07df306565.css?body=1" 
Started request to "http://lvh.me:3000/assets/jquery.self-660adc51e0224b731d29f575a6f1ec167ba08ad06ed5deca4f1e8654c135bf4c.js?body=1" 
Started request to "http://lvh.me:3000/assets/bootstrap/transition.self-6ad2488465135ab731a045a8ebbe3ea2fc501aed286042496eda1664fdd07ba9.js?body=1"

更多进步!它现在包括子域,端口,并指向端口3000.我仍然得到相同的错误:

  1) user creates product successfully
     Failure/Error: fill_in 'user[email]', with: user.email

     Capybara::ElementNotFound:
       Unable to find field "user[email]"

该SO线程的评论之一是:

“这非常有效。另外,如果您使用的是像lvh.me这样的公共域,您可以使用Capybara.server_port = 31234自动设置端口,然后使用set_host”lvh.me:31234“

所以在我的rails_helper中,我在设置app_host:

的下方添加了server_port
Capybara.app_host = 'http://lvh.me/'
Capybara.server_port = 31234

并将before(:each)更改为使用端口31234.结果相同:

"Visit(http://subdomain6.lvh.me:31234/users/sign_in)" started page load 
Started request to "http://subdomain6.lvh.me:31234/users/sign_in" 
Finished "Visit(http://subdomain6.lvh.me:31234/users/sign_in)" with response "Success()" 
Started request to "http://lvh.me:31234/" 
Received 302 from "http://subdomain6.lvh.me:31234/users/sign_in" 
Started request to "http://lvh.me:31234/assets/application-2f17abe5cd0f04e7f5455c4ae0a6e536b5d84dd05e600178874c6a5938ac0804.css" 
Started request to "http://lvh.me:31234/assets/application-8e1c2330cf761b5bfefcaa648b8994224c7c6a87b2f76475831c76474ddca9d1.js" 
Received 200 from "http://lvh.me:31234/" 
Received 200 from "http://lvh.me:31234/assets/application-8e1c2330cf761b5bfefcaa648b8994224c7c6a87b2f76475831c76474ddca9d1.js" 
Received 200 from "http://lvh.me:31234/assets/application-2f17abe5cd0f04e7f5455c4ae0a6e536b5d84dd05e600178874c6a5938ac0804.css" 
...
Also see this repeated about 100 times (in this and in all previous examples):
Wrote response true "" 
Received "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[normalize-space(string(.)) = 'user[email]']/@for)] | .//label[normalize-space(string(.)) = 'user[email]']//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" 
Started "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[normalize-space(string(.)) = 'user[email]']/@for)] | .//label[normalize-space(string(.)) = 'user[email]']//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" 
Finished "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[normalize-space(string(.)) = 'user[email]']/@for)] | .//label[normalize-space(string(.)) = 'user[email]']//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" with response "Success()" 
Wrote response true "" 
Received "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[contains(normalize-space(string(.)), 'user[email]')]/@for)] | .//label[contains(normalize-space(string(.)), 'user[email]')]//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" 
Started "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[contains(normalize-space(string(.)), 'user[email]')]/@for)] | .//label[contains(normalize-space(string(.)), 'user[email]')]//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" 
Finished "FindXpath(.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')][(((./@id = 'user[email]' or ./@name = 'user[email]') or ./@placeholder = 'user[email]') or ./@id = //label[contains(normalize-space(string(.)), 'user[email]')]/@for)] | .//label[contains(normalize-space(string(.)), 'user[email]')]//.//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')])" with response "Success()" 
...

但是,唉,得到相同的结果:

Failures:

  1) user creates product successfully
     Failure/Error: fill_in 'user[email]', with: user.email

     Capybara::ElementNotFound:
       Unable to find field "user[email]"

它似乎应该有效,但必须有我遗漏的东西。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:1)

从您的日志中看起来您的请求是" http://subdomain6.lvh.me:31234/users/sign_in"正在重定向到" http://lvh.me:31234/"如果帐户不存在会发生这种情况。我猜你没有禁用交易测试,这意味着应用程序实际上无法查看测试线程中创建的记录。请参阅 - https://github.com/jnicklas/capybara#transactions-and-database-setuphttps://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example