Read more

Heads up: You should always use "current_window.resize_to" to resize the browser window in tests

Michael Leimstädtner
February 06, 2024Software engineer at makandra GmbH

I recently noticed a new kind of flaky tests on the slow free tier GitHub Action runners: Integration tests were running on smaller screen sizes than specified in the device metrics. The root cause was the use of Selenium's page.driver.resize_window_to methods, which by design Show archive.org snapshot does not block until the resizing process has settled:

We discussed this issue again recently, and agreed that windows size manipulation operations are asynchronous by nature, because we can't control window rendering effects added by a window manager, and browsers don't provide a callback that would allow to detect the end of windows size changing process.

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

Capybara provides a better way to manipulate the browser window which you should use instead: page.current_window.resize_to.

Good: Capybara::Window#resize_to

page.current_window.resize_to(width, height)

Reason

Capybara#Window#resize_to blocks until the window size equals the expected dimensions.

# Source code of #resize_to for additional context:
class Capybara::Window
  def resize_to(width, height)
    wait_for_stable_size { @driver.resize_window_to(handle, width, height) }
  end
  
  def wait_for_stable_size(seconds = session.config.default_max_wait_time)
    res = yield if block_given?
    timer = Capybara::Helpers.timer(expire_in: seconds)
    loop do
      prev_size = size
      sleep 0.025
      return res if prev_size == size
      break if timer.expired?
    end
    raise Capybara::WindowError, "Window size not stable within #{seconds} seconds."
  end
end

Bad: Capybara::Selenium::Driver#resize_window_to

page.driver.resize_window_to(page.driver.current_window_handle, width, height)

Reason

It's the same as above, but without a hard requirement for the resizing to settle. It will wait for at most 250ms before moving on.

# Source code of #resize_window_to for additional context:
class Capybara::Selenium::Driver

  def resize_window_to(handle, width, height)
    within_given_window(handle) do
      browser.manage.window.resize_to(width, height)
    end
  rescue Selenium::WebDriver::Error::UnknownError => e
    raise unless e.message.include?('failed to change window state')

    # Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
    # and raises unnecessary error. Wait a bit and try again.
    sleep 0.25
    within_given_window(handle) do
      browser.manage.window.resize_to(width, height)
    end
  end
end
 
class Selenium::WebDriver::Window
  def resize_to(width, height)
    @bridge.resize_window Integer(width), Integer(height)
  end
end
 
class Selenium::WebDriver::Remote::Bridge
  def resize_window(width, height, handle = :current)
    raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current

   # Chromedriver API call named "set_window_rect"
    set_window_rect(width: width, height: height)
  end
end

A note on device emulations

We usually define multiple device emulations with different screen resolutions in our Capybara config:

Capybara.register_driver(:desktop) { ... }
Capybara.register_driver(:tablet) { ... }
Capybara.register_driver(:mobile) { ... }

If you resize your entire spec using the driver: :tablet tag, you should be fine as we didn't notice any issues yet. Otherwise you could resize your window before every example just to be sure:

RSpec.configure do |config|
  config.before(type: :feature) do
    case Capybara.current_driver
    when :desktop
      resize_browser_to(DESKTOP_WIDTH, DESKTOP_HEIGHT)
    when :tablet
      resize_browser_to(TABLET_WIDTH, TABLET_HEIGHT)
    when :mobile
      resize_browser_to(MOBILE_WIDTH, MOBILE_HEIGHT)
    else
      # Nothing to do in case of rack tests
    end
  end
end

This could however reduce your test performance.

Michael Leimstädtner
February 06, 2024Software engineer at makandra GmbH
Posted by Michael Leimstädtner to makandra dev (2024-02-06 09:58)