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

Posted 3 months ago. Visible to the public.

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.

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
Last edit
2 months ago
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2024-02-06 08:58)