Skip to content

window_handles counts worker targets as windows since Ferrum 0.18, breaking window_opened_by #326

Description

@mockdeep

Affects: cuprite 0.18 with ferrum 0.18.0
Works in: cuprite 0.17 with ferrum 0.17.2

Summary

Any page that spawns a dedicated web worker now reports extra window handles. window_opened_by raises Capybara::WindowError because it sees the worker target appear alongside the real tab:

block passed to #window_opened_by opened 2 windows instead of 1

Ferrum 0.18.0 added worker support (#613) and broadened the tracked target types:

# ferrum-0.18.0/lib/ferrum/contexts.rb:14
ALLOWED_TARGET_TYPES = %w[page iframe worker shared_worker service_worker].freeze

Those all land in the same Context#targets map. Cuprite's window_handles returns that map's keys unfiltered, so workers (and, by the same path, OOPIF iframes and service workers) are now treated as browser windows:

# cuprite-0.18/lib/capybara/cuprite/browser.rb:111
def window_handles
  targets.keys
end

Reproduction

# frozen_string_literal: true

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "capybara"
  gem "cuprite", ENV.fetch("CUPRITE_VERSION", "0.18")
  gem "ferrum", ENV.fetch("FERRUM_VERSION", "0.18.0")
  gem "puma"
end

require "capybara"
require "capybara/cuprite"

def page_with_worker(body)
  <<~HTML
    <!doctype html>
    <html><body>
      #{body}
      <script>
        const blob = new Blob(["self.onmessage = () => {}"], { type: "text/javascript" })
        new Worker(URL.createObjectURL(blob))
      </script>
    </body></html>
  HTML
end

APP = lambda do |env|
  body = if env["PATH_INFO"] == "/other"
           page_with_worker("<h1>Other page</h1>")
         else
           page_with_worker('<a href="/other" target="_blank">Open tab</a>')
         end
  [200, { "content-type" => "text/html" }, [body]]
end

Capybara.register_driver(:cuprite) { |app| Capybara::Cuprite::Driver.new(app, headless: true) }
Capybara.server = :puma, { Silent: true }

session = Capybara::Session.new(:cuprite, APP)
session.visit "/"

puts "cuprite #{Gem.loaded_specs['cuprite'].version}  ferrum #{Gem.loaded_specs['ferrum'].version}"
puts "window_handles before click: #{session.driver.browser.window_handles.size}"

begin
  new_window = session.window_opened_by { session.click_link "Open tab" }
  session.within_window(new_window) { puts "RESULT: ok" }
rescue Capybara::WindowError => e
  puts "RESULT: #{e.class}: #{e.message}"
end

puts "window_handles after click:  #{session.driver.browser.window_handles.size}"
session.driver.browser.targets.each_value { |t| puts "  type=#{t.type.inspect} url=#{t.url.inspect}" }

session.driver.quit

cuprite 0.18 / ferrum 0.18.0 — fails

window_handles before click: 2
RESULT: Capybara::WindowError: block passed to #window_opened_by opened 2 windows instead of 1
window_handles after click:  4
  type="page"   url="http://127.0.0.1:51927/"
  type="worker" url="blob:http://127.0.0.1:51927/1df799cf-..."
  type="page"   url="http://127.0.0.1:51927/other"
  type="worker" url="blob:http://127.0.0.1:51927/b530c457-..."

cuprite 0.17 / ferrum 0.17.2 — passes

CUPRITE_VERSION=0.17 FERRUM_VERSION=0.17.2 ruby repro.rb
window_handles before click: 1
RESULT: ok
window_handles after click:  2
  type="page" url="http://127.0.0.1:51942/"
  type="page" url="http://127.0.0.1:51942/other"

Expected

window_handles returns only page targets, so window_opened_by sees exactly the one tab the block opened, as it did in 0.17.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions