From 2f2d82247156cfb8e169fe8e714bafdc6310f9fc Mon Sep 17 00:00:00 2001 From: Thomas Walpole Date: Sat, 15 Jun 2019 14:17:08 -0700 Subject: [PATCH] Add w3c_click_offset setting to allow click offsets from element centers --- lib/capybara.rb | 2 + lib/capybara/node/element.rb | 13 ++++--- lib/capybara/rack_test/node.rb | 5 ++- lib/capybara/selenium/node.rb | 12 +++++- lib/capybara/session/config.rb | 4 +- lib/capybara/spec/session/node_spec.rb | 53 +++++++++++++++++++------- lib/capybara/spec/spec_helper.rb | 1 + 7 files changed, 68 insertions(+), 22 deletions(-) diff --git a/lib/capybara.rb b/lib/capybara.rb index e74b08a4..b13c9bc1 100644 --- a/lib/capybara.rb +++ b/lib/capybara.rb @@ -97,6 +97,7 @@ module Capybara # and {configure raise_server_errors} is `true`. # - **test_id** (Symbol, String, `nil` = `nil`) - Optional attribute to match locator against with built-in selectors along with id. # - **threadsafe** (Boolean = `false`) - Whether sessions can be configured individually. + # - **w3c_click_offset** (Boolean = 'false') - Whether click offsets should be from element center (true) or top left (false) # # #### DSL Options # @@ -506,4 +507,5 @@ Capybara.configure do |config| config.predicates_wait = true config.default_normalize_ws = false config.allow_gumbo = false + config.w3c_click_offset = false end diff --git a/lib/capybara/node/element.rb b/lib/capybara/node/element.rb index 8735fe5d..3dbbb918 100644 --- a/lib/capybara/node/element.rb +++ b/lib/capybara/node/element.rb @@ -157,13 +157,16 @@ module Capybara # Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element. # @overload $0(*modifier_keys, wait: nil, **offset) # @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking - # @option offset [Integer] x X coordinate to offset the click location from the top left corner of the element - # @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element + # @option options [Integer] x X coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the + # offset will be from the element center, otherwise it will be from the top left corner of the element + # @option options [Integer] y Y coordinate to offset the click location. If {Capybara.configure w3c_click_offset} is `true` the + # offset will be from the element center, otherwise it will be from the top left corner of the element # @return [Capybara::Node::Element] The element - def click(*keys, wait: nil, **offset) - raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y] + def click(*keys, wait: nil, **options) + raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ options[:x] ^ options[:y] - synchronize(wait) { base.click(Array(keys), offset) } + options[:offset] = :center if session_options.w3c_click_offset + synchronize(wait) { base.click(Array(keys), options) } self end diff --git a/lib/capybara/rack_test/node.rb b/lib/capybara/rack_test/node.rb index f44eb643..9f52c4df 100644 --- a/lib/capybara/rack_test/node.rb +++ b/lib/capybara/rack_test/node.rb @@ -63,8 +63,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node native.remove_attribute('selected') end - def click(keys = [], **offset) - raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && offset.empty? + def click(keys = [], **options) + options.delete(:offset) + raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty? if link? follow_link diff --git a/lib/capybara/selenium/node.rb b/lib/capybara/selenium/node.rb index 5a34c0da..7d49eafe 100644 --- a/lib/capybara/selenium/node.rb +++ b/lib/capybara/selenium/node.rb @@ -328,7 +328,13 @@ private end def action_with_modifiers(click_options) - actions = browser_action.move_to(native, *click_options.coords) + actions = browser_action.tap do |acts| + if click_options.center_offset? && click_options.coords? + acts.move_to(native).move_by(*click_options.coords) + else + acts.move_to(native, *click_options.coords) + end + end modifiers_down(actions, click_options.keys) yield actions modifiers_up(actions, click_options.keys) @@ -483,6 +489,10 @@ private [options[:x], options[:y]] end + def center_offset? + options[:offset] == :center + end + def empty? keys.empty? && !coords? end diff --git a/lib/capybara/session/config.rb b/lib/capybara/session/config.rb index 8a751883..1ed19370 100644 --- a/lib/capybara/session/config.rb +++ b/lib/capybara/session/config.rb @@ -8,7 +8,7 @@ module Capybara automatic_reload match exact exact_text raise_server_errors visible_text_only automatic_label_click enable_aria_label save_path asset_host default_host app_host server_host server_port server_errors default_set_options disable_animation test_id - predicates_wait default_normalize_ws].freeze + predicates_wait default_normalize_ws w3c_click_offset].freeze attr_accessor(*OPTIONS) @@ -59,6 +59,8 @@ module Capybara # See {Capybara.configure} # @!method default_normalize_ws # See {Capybara.configure} + # @!method w3c_click_offset + # See {Capybara.configure} remove_method :server_host diff --git a/lib/capybara/spec/session/node_spec.rb b/lib/capybara/spec/session/node_spec.rb index ad63cb9b..4eb8d0f1 100644 --- a/lib/capybara/spec/session/node_spec.rb +++ b/lib/capybara/spec/session/node_spec.rb @@ -636,25 +636,52 @@ Capybara::SpecHelper.spec 'node' do expect { obscured.click(wait: 0) }.to(raise_error { |e| expect(e).to be_an_invalid_element_error(@session) }) end - context "offset" do + context 'offset', requires: [:js] do before do @session.visit('/offset') @clicker = @session.find(:id, 'clicker') end - it 'should offset from top left of element' do - @clicker.click(x: 10, y: 5) - expect(@session).to have_text(/clicked at 110,105/) + context 'when w3c_click_offset is false' do + before do + Capybara.w3c_click_offset = false + end + + it 'should offset from top left of element' do + @clicker.click(x: 10, y: 5) + expect(@session).to have_text(/clicked at 110,105/) + end + + it 'should offset outside the element' do + @clicker.click(x: -15, y: -10) + expect(@session).to have_text(/clicked at 85,90/) + end + + it 'should default to click the middle' do + @clicker.click + expect(@session).to have_text(/clicked at 150,150/) + end end - it 'should offset outside the element' do - @clicker.click(x: -15, y: -10) - expect(@session).to have_text(/clicked at 85,90/) - end + context 'when w3c_click_offset is true' do + before do + Capybara.w3c_click_offset = true + end - it 'should default to click the middle' do - @clicker.click - expect(@session).to have_text(/clicked at 150,150/) + it 'should offset from center of element' do + @clicker.click(x: 10, y: 5) + expect(@session).to have_text(/clicked at 160,155/) + end + + it 'should offset outside from center of element' do + @clicker.click(x: -65, y: -60) + expect(@session).to have_text(/clicked at 85,90/) + end + + it 'should default to click the middle' do + @clicker.click + expect(@session).to have_text(/clicked at 150,150/) + end end end end @@ -692,7 +719,7 @@ Capybara::SpecHelper.spec 'node' do expect { obscured.double_click }.not_to raise_error end - context "offset" do + context 'offset', requires: [:js] do before do @session.visit('/offset') @clicker = @session.find(:id, 'clicker') @@ -748,7 +775,7 @@ Capybara::SpecHelper.spec 'node' do expect { obscured.right_click }.not_to raise_error end - context "offset" do + context 'offset', requires: [:js] do before do @session.visit('/offset') @clicker = @session.find(:id, 'clicker') diff --git a/lib/capybara/spec/spec_helper.rb b/lib/capybara/spec/spec_helper.rb index 61adf0be..dff9d8bb 100644 --- a/lib/capybara/spec/spec_helper.rb +++ b/lib/capybara/spec/spec_helper.rb @@ -36,6 +36,7 @@ module Capybara Capybara.predicates_wait = true Capybara.default_normalize_ws = false Capybara.allow_gumbo = true + Capybara.w3c_click_offset = false reset_threadsafe end