Skip to content

[🐛 Bug]: Ruby: WebSocket hangs when receiving payload larger than WebSocket.max_frame_size #17264

@alpaca-tc

Description

@alpaca-tc

Description

When using Selenium with Ruby, the WebSocket connection used for DevTools (CDP) can hang if it receives a payload larger than WebSocket.max_frame_size (default: 20MB).

This issue is difficult to detect because, by default, no error is raised and the process appears to hang silently.

Root Cause

The issue is caused by the behavior of websocket-ruby and how it is used in Selenium.

As a result, the WebSocket reading loop effectively becomes stuck, and the process appears to hang.

Reproduction

This issue occurs when a large payload is sent over the DevTools WebSocket connection.

The easiest way to reproduce is to reduce the frame size limit:

WebSocket.max_frame_size = 1

Then trigger any CDP message.

In my case, the issue occurred under the following conditions:

  1. Use DevTools request interception (e.g., driver.intercept)
  1. Display a large preview image in the browser using a data:image/... URL
  2. CDP emits events such as:
  • Network.responseReceived
  • Network.requestWillBeSent
  • These include the large data: URL in the payload.
  1. The Ruby process hangs

Expected Behavior

When a frame exceeds WebSocket.max_frame_size, one of the following should happen:

  • Raise an error by default, or at least provide a clear signal
  • Log a warning or error message
  • Avoid silently continuing with a broken decoding state

Actual Behavior

  • No exception is raised by default
  • incoming_frame.next continuously returns nil
  • The WebSocket read loop continues indefinitely
  • The process appears to hang with no clear indication of the problem

Workarounds

  • Increase the frame size limit: WebSocket.max_frame_size = 100 * 1024 * 1024
  • Enable exception raising: WebSocket.should_raise = true
  • Avoid large CDP payloads
  • For example, disable Network events if they are not needed: driver.browser.devtools.network.disable
    • NOTE: this may break functionality that depends on network events, such as request cancellation tracking.

Suggestion

The main problem is that users cannot easily detect what is happening.

It would be helpful if Selenium:

  • Raised an error when decoding fails repeatedly, or
  • Logged a warning when frames are dropped or cannot be decoded

Additionally, it would be useful if CDP event subscriptions could be more granular, so that unnecessary Network.* events with large payloads do not need to be received when they are not needed.

Additional Notes

I apologize if this issue has already been reported.
If there is a better solution or a recommended approach, I would appreciate any guidance.

Reproducible Code

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "capybara"
  gem "selenium-devtools"
  gem "selenium-webdriver"
  gem "puma"
  gem "rackup"
end

require 'capybara'
require 'capybara/dsl'
require 'rack'
require 'rack/static'
require 'websocket'

html = <<~HTML
<!doctype html>

<html lang="en">
  <body>
    <form>
      <input type="file" id="file" name="file" />
      <img id="preview" src="" alt="Image preview" style="display: none; max-width: 200px; margin-top: 10px;" />
    </form>
  </body>
  <script>
    const form = document.querySelector('form');
    const file = document.querySelector('#file');
    const preview = document.querySelector('#preview');

    file.addEventListener('change', () => {
      const selectedFile = file.files[0];
      if (selectedFile) {
        const reader = new FileReader();
        reader.onload = (e) => {
          preview.src = e.target.result;
          preview.style.display = 'block';
        };
        reader.readAsDataURL(selectedFile);
      } else {
        preview.src = '';
        preview.style.display = 'none';
      }
    });
  </script>
</html>
HTML

Capybara.default_driver = :selenium_chrome_headless
Capybara.app = Rack::ContentLength.new(
  proc { [200, { "content-type" => "text/html" }, [html]] }
)

class Test
  include Capybara::DSL

  def run
    # The following code causes the issue, but if you comment it out, it works fine.
    page.driver.browser.intercept do |request, &continue|
      continue.call(request)
    end

    WebSocket.max_frame_size = 1024

    visit '/'

    image = Tempfile.new(["avatar", ".png"]) do |f|
      f.binmode
      f.write OpenURI.open_uri("https://github.com/alpaca-tc.png").read
      f.flush
    end

    attach_file("file", image.path)
  end
end

Test.new.run

ℹ️ Last known working version: selenium-webdriver-4.41.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions