使用Poltergeist,Phantom JS实例在每次rspec运行期间都不会退出

时间:2013-11-20 21:23:48

标签: poltergeist

在rspec执行的每个poltergeist测试中,如果我使用:

创建一个新会话

Capybara.session_name="some_session_name"

phantomjs实例作为子进程启动,并且在测试结束之前永远不会退出,从而导致构建服务器上出现OOM。

我认为这是由于未能调用driver.quit,如readme of Poltergeist中所述:

  

如果您手动运行几个水豚会话,请确保在不再需要会话时调用session.driver.quit。忘记这一点会导致内存泄漏,系统的资源可能会比您预期的更早耗尽。

但是,我在测试的page.driver.quit块中调用了after

下面是我的后块代码。 $adhoc_sessions是我每次设置Capybara.session_name时填充的全局变量,其值与Capybara.session_name上设置的值相匹配。

config.after(:each) do
  if example.metadata[:js]

    $adhoc_sessions.each do |session_name|
      Capybara.using_session( session_name ) do
      page.driver.quit
      end
    end
    $adhoc_sessions.clear
  end

有关我在这里可以做得更好的建议吗?我没有打电话给一些清理命令吗?

1 个答案:

答案 0 :(得分:1)

我找到了一个来自两个约束的解决方案:

  1. 我认为你不能在没有的情况下安全地在Capybara打电话给 driver.quit 获得访问私人 @session_pool 的权限,因为Capybara有 无法让用户在放入会话后从池中删除会话 因此,如果您在会话中调用driver.quit,则无法删除该会话 从池中,最终Capybara会尝试重置!会话,造成 Poltergeist抛出一个IOError,因为它是内部的 没有连接websockets上的通信。
  2. 如果你这样做了 在每次测试运行后敲击整个会话池,并退出每个 最终,当你这样做的时候,每个会话中都会出现吵闹的司机 你会遇到 TOO MANY OPEN FILES 错误。 。即,:
  3. 重新创建TOO MANY OPEN FILES错误的方法错误 - 请勿使用此颜色!!

    # you have to do quite a few test runs to cause the open files error
    config.append_after(:each) do
      session_pool = Capybara.instance_variable_get("@session_pool")
      session_pool.each do | key, value |
        value.driver.quit
      end
      session_pool.clear
    end    
    

    我认为这是一个真正的恶作剧者,但我不在乎......这就是为什么......在运行上面的代码时,我注意到创建一个恶作剧会话是一个明显缓慢且资源密集的操作。所以,我认为我宁愿拥有一个永远不会消失的会议池......就像Capybara的设计方式一样。

    这种方法的唯一问题就是像我一样使用Capybara.session_name,这是在每个测试的基础上提出任意测试名称。也许在一个测试中,我希望每个session_name与用户的数据库ID相同。或者也许我想出了我在测试中使用的5个常数,以及5个不同的常量用于不同的测试。换句话说,我可以在我的测试套件中使用100s的session_name,但是对于任何给定测试的人来说,我最多只有一些会话。所以一个好的解决方案重用了poltergeist会话,但是让我在每次测试运行中使用任意会话名称。

    这是我的解决方案

    规格/ utilities.rb

    # holds a single test's session name's, mapped to pooled session names
    $capybara_session_mapper = {}
    
    # called after each test,
    # to make sure each test run has it's own map of session names
    def reset_session_mapper
      $capybara_session_mapper.clear
    end
    
    # manages the mapped session name
    def mapped_session_name(session_name)
      return :default if session_name == :default # special treatment for the built-in session
      $capybara_session_mapper[session_name] ||= $capybara_session_mapper.length
    end
    
    # in place of ever using Capybara.session_name directly, 
    # this utility is used to handle the mapping of session names in a way across all tests runs
    def in_client(name)  
      Capybara.session_name = mapped_session_name(session_name)
    
      yield
    end
    

    在* spec_helper.rb *:

    config.after(:each) do
      Capybara.reset_sessions!
      reset_session_mapper
    end
    

    直接使用in_client而不是Capybara.session_name的示例测试:

    it "can't see a private thing until it is made public" do
    
      in_client(user1.id) do
        visit '/some/private/thing'
        expect(page).to have_selector('#private-notice')
      end
    
      in_client(user2.id) do
        visit '/expose/some/private/thing'
      end
    
      in_client(user1.id) do
        visit '/some/private/thing`
        expect(page).to have_selector('#private-content')
      end
    end
    

    -- copied from my github answer