From dce6322123b85ad5cb19ed554be662015968c903 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Fri, 11 Sep 2026 10:02:46 -0400 Subject: [PATCH 1/2] `send_keys`: skip `click` if `Node` has focus The current `Capybara::Cuprite::Page#send_keys` implementation attempts to trigger a `click` on the specified `node` argument. This commit aims to avoid that click if the element already has focus. For example, if _no_ element has focus, it is possible for the `document.body` element to serve as the focus fallback. After this change, a bare `send_keys` will not attempt to click on the `document.body` element, but will still trigger the keystrokes. --- CHANGELOG.md | 2 ++ lib/capybara/cuprite/javascripts/index.js | 4 ++++ lib/capybara/cuprite/page.rb | 2 +- spec/features/driver_spec.rb | 4 ++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bed814..71aead5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Changed +- Change `Node#send_keys` to avoid triggering `click` on an already focused element [#327] + ### Fixed ### Removed diff --git a/lib/capybara/cuprite/javascripts/index.js b/lib/capybara/cuprite/javascripts/index.js index 092c022..dbae80a 100644 --- a/lib/capybara/cuprite/javascripts/index.js +++ b/lib/capybara/cuprite/javascripts/index.js @@ -523,6 +523,10 @@ class Cuprite { window.getSelection().deleteFromDocument(); } + hasFocus(node) { + return document.activeElement === node; + } + containsSelection(node) { let selectedNode = document.getSelection().focusNode; diff --git a/lib/capybara/cuprite/page.rb b/lib/capybara/cuprite/page.rb index fa34251..f8e9e4f 100644 --- a/lib/capybara/cuprite/page.rb +++ b/lib/capybara/cuprite/page.rb @@ -66,7 +66,7 @@ def hover(node) end def send_keys(node, keys) - unless evaluate_on(node: node, expression: %(_cuprite.containsSelection(this))) + unless evaluate_on(node: node, expression: %(_cuprite.containsSelection(this) || _cuprite.hasFocus(this))) before_click(node, "click") node.click(mode: :left, keys: keys) end diff --git a/spec/features/driver_spec.rb b/spec/features/driver_spec.rb index aad2929..43b937f 100644 --- a/spec/features/driver_spec.rb +++ b/spec/features/driver_spec.rb @@ -1331,6 +1331,9 @@ def create_screenshot(file, *args) it "sends keys to the active_element" do @session.find_field("empty_input").execute_script("this.focus()") + @session.find_field("empty_input").execute_script <<~JS + this.addEventListener("click", () => window.clickedBeforeFocus = true) + JS expect(@session).to have_field("empty_input", focused: true) @@ -1338,6 +1341,7 @@ def create_screenshot(file, *args) expect(@session).to have_field("empty_input", focused: false) .and(have_field("filled_input", focused: true)) + expect(@session.evaluate_script("window.clickedBeforeFocus")).to be_nil end it "sends keys to filled contenteditable div" do From d9788d0d09a158c177d50d7e8e8bf59a7df42045 Mon Sep 17 00:00:00 2001 From: Dmitry Vorotilin Date: Fri, 16 Oct 2026 13:19:08 +0300 Subject: [PATCH 2/2] test: run Capybara's active_element and focused specs --- CHANGELOG.md | 5 +++-- spec/spec_helper.rb | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a24b3..950114d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ ### Changed - Ferrum is bumped to `~> 0.19.0` - -- Change `Node#send_keys` to avoid triggering `click` on an already focused element [#327] +- Change `Node#send_keys` to avoid triggering `click` on an already focused element. `send_keys(:tab)` without an + element no longer clicks the middle of the page first, so Tab moves focus from the start of the page, and Capybara's + `#active_element` and `focused:` specs run in the suite [#327] ### Fixed - `window_size: nil` raised `Ferrum::BrowserError: Invalid parameters` on every page instead of leaving the window diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c5986b5..74f9f6e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -63,7 +63,6 @@ module TestSessions metadata[:skip] = true if metadata[:full_description].match(/#{regexes}/) metadata[:skip] = "Intentionally skipped" if metadata[:full_description].match(/#{intentional_skip}/) - metadata[:skip] = true if metadata[:requires]&.include?(:active_element) end config.around do |example|