diff --git a/lib/capybara/driver/base.rb b/lib/capybara/driver/base.rb index b985ff87..b4097048 100644 --- a/lib/capybara/driver/base.rb +++ b/lib/capybara/driver/base.rb @@ -42,6 +42,10 @@ class Capybara::Driver::Base raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script' end + def evaluate_async_script(script, *args) + raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script_asnyc' + end + def save_screenshot(path, options={}) raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#save_screenshot' end diff --git a/lib/capybara/selenium/driver.rb b/lib/capybara/selenium/driver.rb index 3a91e031..068c6b87 100644 --- a/lib/capybara/selenium/driver.rb +++ b/lib/capybara/selenium/driver.rb @@ -114,6 +114,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base unwrap_script_result(result) end + def evaluate_async_script(script, *args) + browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time + result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} ) + unwrap_script_result(result) + end + def save_screenshot(path, _options={}) browser.save_screenshot(path) end diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index e80a963a..74a39d1f 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -642,6 +642,24 @@ module Capybara element_script_result(result) end + ## + # + # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script. + # + # @param [String] script A string of JavaScript to evaluate + # @return [Object] The result of the evaluated JavaScript (may be driver specific) + # + def evaluate_async_script(script, *args) + @touched = true + result = if args.empty? + driver.evaluate_async_script(script) + else + raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_async_script arguments" if driver.method(:evaluate_async_script).arity == 1 + driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} ) + end + element_script_result(result) + end + ## # # Execute the block, accepting a alert. diff --git a/lib/capybara/spec/session/evaluate_async_script_spec.rb b/lib/capybara/spec/session/evaluate_async_script_spec.rb new file mode 100644 index 00000000..e9b337a7 --- /dev/null +++ b/lib/capybara/spec/session/evaluate_async_script_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +Capybara::SpecHelper.spec "#evaluate_async_script", requires: [:js] do + it "should evaluate the given script and return whatever it produces" do + @session.visit('/with_js') + expect(@session.evaluate_async_script("arguments[0](4)")).to eq(4) + end + + it "should support passing elements as arguments to the script", requires: [:js, :es_args] do + @session.visit('/with_js') + el = @session.find(:css, '#drag p') + result = @session.evaluate_async_script("arguments[2]([arguments[0].innerText, arguments[1]])", el, "Doodle Funk") + expect(result).to eq ["This is a draggable element.", "Doodle Funk"] + end + + it "should support returning elements after a timeout", requires: [:js, :es_args] do + @session.visit('/with_js') + @session.find(:css, '#change') # ensure page has loaded and element is available + el = @session.evaluate_async_script("var cb = arguments[0]; setTimeout(function(){ cb(document.getElementById('change')) }, 100)") + expect(el).to be_instance_of(Capybara::Node::Element) + expect(el).to eq(@session.find(:css, '#change')) + end + + it "will timeout if the script takes too long" do + @session.visit('/with_js') + expect do + @session.using_wait_time(1) do + @session.evaluate_async_script("var cb = arguments[0]; setTimeout(function(){ cb(null) }, 3000)") + end + end.to raise_error + end +end