При использовании Poltergeist экземпляры Phantom JS не завершаются при каждом запуске rspec.

При каждом тесте на полтергейст, который выполняется rspec, если я создаю новый сеанс, используя:

Capybara.session_name="some_session_name"

экземпляр phantomjs запускается как подпроцесс и никогда не завершается до окончания теста, вызывая OOM на моем сервере сборки.

Я полагаю, что это связано с тем, что не удалось вызвать driver.quit, как описано в ознакомлении с 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

Любые предложения о том, что я мог бы сделать лучше здесь? Я не могу вызвать какую-то команду очистки?


person sethcall    schedule 20.11.2013    source источник
comment
Прождав несколько дней здесь, я также подал ошибку. Поскольку я звоню page.driver.quit, на мой взгляд, я поступаю правильно. github.com/jonleighton/poltergeist/issues/419   -  person sethcall    schedule 26.11.2013


Ответы (1)


Я нашел решение, основанное на двух ограничениях:

  1. Я не думаю, что вы можете безопасно вызывать driver.quit в Capybara без доступа к частному @session_pool, потому что Capybara не позволяет пользователям удалять сеанс из Если вы вызовете команду driver.quit для сеанса, вы не сможете удалить этот сеанс из пула, и в конечном итоге Capybara попытается сбросить! сеанс, вызывая полтергейст. выдать IOError, потому что внутренняя связь через веб-сокеты не подключена.
  2. Если вместо этого вы будете разбивать весь пул сеансов после каждого тестового запуска и закрывать каждый драйвер полтергейста в каждом сеансе, в конечном итоге вы столкнетесь с ошибкой СЛИШКОМ МНОГО ОТКРЫТЫХ ФАЙЛОВ. то есть,:

Метод воссоздания ошибки "СЛИШКОМ МНОГО ОТКРЫТЫХ ФАЙЛОВ" - не используйте его!!

# 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 так, как это делаю я, то есть придумывать произвольные имена тестов для каждого теста. Возможно, в одном тесте я хочу, чтобы каждое имя_сессии совпадало с идентификатором базы данных пользователя. Или, может быть, я придумал 5 констант, которые я использую в тесте, и 5 разных констант для другого теста. Другими словами, я могу использовать 100 сеансов имени_сеанса в своем наборе тестов, но у меня всегда есть максимум всего несколько сеансов для любого заданного теста. Таким образом, хорошее решение повторно использует сеансы полтергейста, но позвольте мне использовать произвольное имя сеанса для каждого запуска теста.

это мое решение

spec/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

-- скопировано из моего ответа на github

person sethcall    schedule 21.12.2013