Read more

Threads and processes in a Capybara/Selenium session

Henning Koch
December 25, 2013Software engineer at makandra GmbH

TLDR: This card explains which threads and processes interact with each other when you run a Selenium test with Capybara. This will help you understand "impossible" behavior of your tests.


Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

When you run a Rack::Test (non-Javascript) test with Capybara, there is a single process in play. It runs both your test script and the server responding to the user interactions scripted by your test.

A Selenium (Javascript) test has a lot more moving parts:

  1. One process runs your test script. This is the process you spawn by running cucumber.
  2. A second thread within the same process runs the server responding to your test.
  3. A second process runs the browser that is scripted by the first thread in the first process.

Most of the time, Capybara/Selenium will hide that complexity from you. But sometimes the complexity will bleed through, causing your tests to show "impossible" behavior.

Here is some unexpected behavior caused by the interaction of threads and processes:

  • It can appear random how the test thread and the server thread pass control to each other. The result is that the server might seems to "hang" while the test thread is busy, or that your test is expecting results before the server thread has finished computing. What your tests should do instead is to wait until the server has responded, e.g. by using wait_until or our patiently helper.
  • A debugger session completely starves the server thread. E.g. you cannot dispatch an AJAX request with page.execute_script and immediately inspect the result in a debugger shell. You need to wait_until the AJAX request completes before launching the debugger.
  • A starved server thread can cause unexpected MySQL deadlocks. E.g. your test triggers a server request, locks some rows (MySQL automatically locks rows for you during standard queries) then a debugger session starves the server thread. If you now run a query on the locked table, MySQL will explode with a detected deadlock. Again you need to wait_until the AJAX request completes before launching the debugger.
  • When you mock the time in the test process (e. g. by using Timecop), the time will remain unchanged for your scripted browser window. Use a solution like timemachine instead.
  • Database changes caused by transactions in the test thread will not be picked up by the server thread until the transaction is committed (finished). This is why we don't use "transactional fixtures" in our Cucumber tests, which Rails by default uses to clean up the database after each test. Instead we use DatabaseCleaner.
Posted by Henning Koch to makandra dev (2013-12-25 00:50)