diff --git a/History.md b/History.md index a855eee0..8295c070 100644 --- a/History.md +++ b/History.md @@ -1,9 +1,14 @@ # Version 3.2.0 Release date: unreleased +### Changed + +* Ruby 2.3.0+ is now required + ### Added * New global configuration `default_set_options` used in `Capybara::Node::Element#set` as default `options` hash [Champier Cyril] +* `execute_javascript' and `evaluate_javascript` can now be called on elements to run the JS in the context of the element [Thomas Walpole] # Version 3.1.0 Release date: 2018-05-10 diff --git a/lib/capybara/node/element.rb b/lib/capybara/node/element.rb index 3e4d454e..f7b851dc 100644 --- a/lib/capybara/node/element.rb +++ b/lib/capybara/node/element.rb @@ -347,6 +347,40 @@ module Capybara self end + ## + # + # Execute the given JS in the context of the element not returning a result. This is useful for scripts that return + # complex objects, such as jQuery statements. +execute_script+ should be used over + # +evaluate_script+ whenever possible. `this` in the script will refer to the element this is called on. + # + # @param [String] script A string of JavaScript to execute + # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers + # + def execute_script(script, *args) + session.execute_script(<<~JS, self, *args) + (function (){ + #{script} + }).apply(arguments[0], Array.prototype.slice.call(arguments,1)); + JS + end + + ## + # + # Evaluate the given JS in the context of the element and return the result. Be careful when using this with + # scripts that return complex objects, such as jQuery statements. +execute_script+ might + # be a better alternative. `this` in the script will refer to the element this is called on. + # + # @param [String] script A string of JavaScript to evaluate + # @return [Object] The result of the evaluated JavaScript (may be driver specific) + # + def evaluate_script(script, *args) + session.evaluate_script(<<~JS, self, *args) + (function(){ + return #{script} + }).apply(arguments[0], Array.prototype.slice.call(arguments,1)); + JS + end + def reload if @allow_reload begin diff --git a/lib/capybara/spec/session/node_spec.rb b/lib/capybara/spec/session/node_spec.rb index 9c0a0c6c..a97c677d 100644 --- a/lib/capybara/spec/session/node_spec.rb +++ b/lib/capybara/spec/session/node_spec.rb @@ -455,6 +455,48 @@ Capybara::SpecHelper.spec "node" do end end + describe "#execute_script", requires: %i[js es_args] do + it "should execute the given script in the context of the element and return nothing" do + @session.visit('/with_js') + expect(@session.find(:css, '#change').execute_script("this.textContent = 'Funky Doodle'")).to be_nil + expect(@session).to have_css('#change', text: 'Funky Doodle') + end + + it "should pass arguments to the script" do + @session.visit('/with_js') + @session.find(:css, '#change').execute_script("this.textContent = arguments[0]", "Doodle Funk") + expect(@session).to have_css('#change', text: 'Doodle Funk') + end + end + + describe "#evaluate_script", requires: %i[js es_args] do + it "should evaluate the given script in the context of the element and return whatever it produces" do + @session.visit('/with_js') + el = @session.find(:css, '#with_change_event') + expect(el.evaluate_script("this.value")).to eq('default value') + end + + it "should pass arguments to the script" do + @session.visit('/with_js') + @session.find(:css, '#change').evaluate_script("this.textContent = arguments[0]", "Doodle Funk") + expect(@session).to have_css('#change', text: 'Doodle Funk') + end + + it "should pass multiple arguments" do + @session.visit('/with_js') + change = @session.find(:css, '#change') + expect(change.evaluate_script("arguments[0] + arguments[1]", 2, 3)).to eq 5 + end + + it "should support returning elements" do + @session.visit('/with_js') + change = @session.find(:css, '#change') # ensure page has loaded and element is available + el = change.evaluate_script("this") + expect(el).to be_instance_of(Capybara::Node::Element) + expect(el).to eq(change) + end + end + describe '#reload', requires: [:js] do context "without automatic reload" do before { Capybara.automatic_reload = false }