capybara 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +19 -0
  3. data/README.md +1 -1
  4. data/lib/capybara.rb +2 -0
  5. data/lib/capybara/config.rb +2 -1
  6. data/lib/capybara/driver/base.rb +1 -1
  7. data/lib/capybara/driver/node.rb +3 -3
  8. data/lib/capybara/node/actions.rb +90 -92
  9. data/lib/capybara/node/base.rb +2 -2
  10. data/lib/capybara/node/document_matchers.rb +5 -5
  11. data/lib/capybara/node/element.rb +47 -16
  12. data/lib/capybara/node/finders.rb +13 -13
  13. data/lib/capybara/node/matchers.rb +18 -17
  14. data/lib/capybara/node/simple.rb +6 -2
  15. data/lib/capybara/queries/ancestor_query.rb +1 -1
  16. data/lib/capybara/queries/base_query.rb +3 -3
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/match_query.rb +8 -0
  19. data/lib/capybara/queries/selector_query.rb +97 -42
  20. data/lib/capybara/queries/sibling_query.rb +1 -1
  21. data/lib/capybara/queries/text_query.rb +12 -7
  22. data/lib/capybara/rack_test/browser.rb +9 -7
  23. data/lib/capybara/rack_test/form.rb +15 -17
  24. data/lib/capybara/rack_test/node.rb +12 -12
  25. data/lib/capybara/result.rb +26 -15
  26. data/lib/capybara/rspec.rb +1 -2
  27. data/lib/capybara/rspec/compound.rb +4 -4
  28. data/lib/capybara/rspec/matchers.rb +2 -2
  29. data/lib/capybara/selector.rb +75 -225
  30. data/lib/capybara/selector/css.rb +2 -2
  31. data/lib/capybara/selector/filter_set.rb +17 -21
  32. data/lib/capybara/selector/filters/base.rb +24 -1
  33. data/lib/capybara/selector/filters/expression_filter.rb +3 -5
  34. data/lib/capybara/selector/filters/node_filter.rb +4 -4
  35. data/lib/capybara/selector/selector.rb +221 -69
  36. data/lib/capybara/selenium/driver.rb +15 -88
  37. data/lib/capybara/selenium/node.rb +25 -28
  38. data/lib/capybara/server.rb +10 -54
  39. data/lib/capybara/server/animation_disabler.rb +43 -0
  40. data/lib/capybara/server/middleware.rb +55 -0
  41. data/lib/capybara/session.rb +29 -30
  42. data/lib/capybara/session/config.rb +11 -1
  43. data/lib/capybara/session/matchers.rb +5 -5
  44. data/lib/capybara/spec/session/assert_text_spec.rb +1 -1
  45. data/lib/capybara/spec/session/body_spec.rb +10 -12
  46. data/lib/capybara/spec/session/click_link_spec.rb +3 -3
  47. data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +1 -1
  48. data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
  49. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  50. data/lib/capybara/spec/session/find_spec.rb +8 -3
  51. data/lib/capybara/spec/session/has_link_spec.rb +2 -2
  52. data/lib/capybara/spec/session/node_spec.rb +50 -0
  53. data/lib/capybara/spec/session/node_wrapper_spec.rb +5 -5
  54. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
  55. data/lib/capybara/spec/session/window/windows_spec.rb +3 -5
  56. data/lib/capybara/spec/spec_helper.rb +4 -2
  57. data/lib/capybara/spec/views/with_animation.erb +46 -0
  58. data/lib/capybara/version.rb +1 -1
  59. data/lib/capybara/window.rb +3 -2
  60. data/spec/filter_set_spec.rb +19 -2
  61. data/spec/result_spec.rb +33 -1
  62. data/spec/rspec/features_spec.rb +6 -10
  63. data/spec/rspec/shared_spec_matchers.rb +4 -4
  64. data/spec/selector_spec.rb +74 -4
  65. data/spec/selenium_spec_marionette.rb +2 -0
  66. data/spec/server_spec.rb +1 -1
  67. data/spec/session_spec.rb +12 -0
  68. data/spec/shared_selenium_session.rb +30 -0
  69. metadata +8 -9
  70. data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
  71. data/.yard/templates_custom/default/class/html/setup.rb +0 -17
  72. data/.yard/yard_extensions.rb +0 -78
  73. data/.yardopts +0 -1
@@ -26,7 +26,7 @@ module Capybara
26
26
  # end
27
27
  #
28
28
  # Session provides a number of methods for controlling the navigation of the page, such as +visit+,
29
- # +current_path, and so on. It also delegate a number of methods to a Capybara::Document, representing
29
+ # +current_path, and so on. It also delegates a number of methods to a Capybara::Document, representing
30
30
  # the current HTML document. This allows interaction:
31
31
  #
32
32
  # session.fill_in('q', with: 'Capybara')
@@ -83,10 +83,10 @@ module Capybara
83
83
  raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
84
84
  yield config
85
85
  end
86
- @server = if config.run_server and @app and driver.needs_server?
87
- Capybara::Server.new(@app, port: config.server_port, host: config.server_host, reportable_errors: config.server_errors).boot
88
- else
89
- nil
86
+ @server = if config.run_server && @app && driver.needs_server?
87
+ server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
88
+ server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
89
+ Capybara::Server.new(@app, server_options).boot
90
90
  end
91
91
  @touched = false
92
92
  end
@@ -126,7 +126,7 @@ module Capybara
126
126
  driver.reset!
127
127
  @touched = false
128
128
  end
129
- @server.wait_for_pending_requests if @server
129
+ @server&.wait_for_pending_requests
130
130
  raise_server_error!
131
131
  end
132
132
  alias_method :cleanup!, :reset!
@@ -189,14 +189,11 @@ module Capybara
189
189
  # Addressable parsing is more lenient than URI
190
190
  uri = ::Addressable::URI.parse(current_url)
191
191
 
192
- # If current_url ends up being nil, won't be able to call .path on a NilClass.
193
- return nil if uri.nil?
194
-
195
192
  # Addressable doesn't support opaque URIs - we want nil here
196
- return nil if uri.scheme == "about"
193
+ return nil if uri&.scheme == "about"
197
194
 
198
- path = uri.path
199
- path if path && !path.empty?
195
+ path = uri&.path
196
+ path unless path&.empty?
200
197
  end
201
198
 
202
199
  ##
@@ -248,11 +245,10 @@ module Capybara
248
245
 
249
246
  visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
250
247
 
251
- uri_base = if @server
252
- ::Addressable::URI.parse(config.app_host || "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}")
253
- else
254
- config.app_host && ::Addressable::URI.parse(config.app_host)
255
- end
248
+ base = config.app_host
249
+ base ||= "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}" if @server
250
+
251
+ uri_base = ::Addressable::URI.parse(base)
256
252
 
257
253
  if uri_base && [nil, 'http', 'https'].include?(visit_uri.scheme)
258
254
  if visit_uri.relative?
@@ -337,7 +333,7 @@ module Capybara
337
333
  new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args)
338
334
  begin
339
335
  scopes.push(new_scope)
340
- yield
336
+ yield if block_given?
341
337
  ensure
342
338
  scopes.pop
343
339
  end
@@ -411,7 +407,7 @@ module Capybara
411
407
  #
412
408
  # @overload within_frame(element)
413
409
  # @param [Capybara::Node::Element] frame element
414
- # @overload within_frame([kind = :frame], locator, options = {})
410
+ # @overload within_frame([kind = :frame], locator, **options)
415
411
  # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
416
412
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
417
413
  # @overload within_frame(index)
@@ -419,7 +415,7 @@ module Capybara
419
415
  def within_frame(*args)
420
416
  switch_to_frame(_find_frame(*args))
421
417
  begin
422
- yield
418
+ yield if block_given?
423
419
  ensure
424
420
  switch_to_frame(:parent)
425
421
  end
@@ -475,9 +471,8 @@ module Capybara
475
471
  # @raise [ArgumentError] if both or neither arguments were provided
476
472
  #
477
473
  def switch_to_window(window = nil, **options, &window_locator)
478
- block_given = block_given?
479
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given
480
- raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given
474
+ raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given?
475
+ raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given?
481
476
  unless scopes.last.nil?
482
477
  raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
483
478
  "`within` or `within_frame` blocks."
@@ -521,7 +516,7 @@ module Capybara
521
516
  end
522
517
 
523
518
  begin
524
- yield
519
+ yield if block_given?
525
520
  ensure
526
521
  _switch_to_window(original) unless original == window_or_proc
527
522
  end
@@ -569,7 +564,7 @@ module Capybara
569
564
  #
570
565
  def execute_script(script, *args)
571
566
  @touched = true
572
- driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
567
+ driver.execute_script(script, *driver_args(args))
573
568
  end
574
569
 
575
570
  ##
@@ -583,7 +578,7 @@ module Capybara
583
578
  #
584
579
  def evaluate_script(script, *args)
585
580
  @touched = true
586
- result = driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
581
+ result = driver.evaluate_script(script, *driver_args(args))
587
582
  element_script_result(result)
588
583
  end
589
584
 
@@ -596,7 +591,7 @@ module Capybara
596
591
  #
597
592
  def evaluate_async_script(script, *args)
598
593
  @touched = true
599
- result = driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
594
+ result = driver.evaluate_async_script(script, *driver_args(args))
600
595
  element_script_result(result)
601
596
  end
602
597
 
@@ -610,11 +605,11 @@ module Capybara
610
605
  # $0 do
611
606
  # click_link('link that triggers appearance of system modal')
612
607
  # end
613
- # @overload $0(text, options = {}, &blk)
608
+ # @overload $0(text, **options, &blk)
614
609
  # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
615
610
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
616
611
  # @yield Block whose actions will trigger the system modal
617
- # @overload $0(options = {}, &blk)
612
+ # @overload $0(**options, &blk)
618
613
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
619
614
  # @yield Block whose actions will trigger the system modal
620
615
  # @return [String] the message shown in the modal
@@ -799,6 +794,10 @@ module Capybara
799
794
 
800
795
  @@instance_created = false
801
796
 
797
+ def driver_args(args)
798
+ args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
799
+ end
800
+
802
801
  def accept_modal(type, text_or_options, options, &blk)
803
802
  driver.accept_modal(type, modal_options(text_or_options, options), &blk)
804
803
  end
@@ -878,7 +877,7 @@ module Capybara
878
877
  driver.switch_to_window handle
879
878
  return Window.new(self, handle) if yield
880
879
  end
881
- rescue => e
880
+ rescue StandardError => e
882
881
  driver.switch_to_window(original_window_handle)
883
882
  raise e
884
883
  else
@@ -7,7 +7,7 @@ module Capybara
7
7
  OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
8
8
  automatic_reload match exact exact_text raise_server_errors visible_text_only
9
9
  automatic_label_click enable_aria_label save_path asset_host default_host app_host
10
- server_host server_port server_errors].freeze
10
+ server_host server_port server_errors default_set_options disable_animation].freeze
11
11
 
12
12
  attr_accessor(*OPTIONS)
13
13
 
@@ -50,6 +50,10 @@ module Capybara
50
50
  # See {Capybara.configure}
51
51
  # @!method server_errors
52
52
  # See {Capybara.configure}
53
+ # @!method default_set_options
54
+ # See {Capybara.configure}
55
+ # @!method disable_animation
56
+ # See {Capybara.configure}
53
57
 
54
58
  remove_method :server_host
55
59
 
@@ -78,6 +82,12 @@ module Capybara
78
82
  @default_host = url
79
83
  end
80
84
 
85
+ remove_method :disable_animation=
86
+ def disable_animation=(bool)
87
+ warn "Capybara.disable_animation is a beta feature - it may change/disappear in a future point version" if bool
88
+ @disable_animation = bool
89
+ end
90
+
81
91
  def initialize_copy(other)
82
92
  super
83
93
  @server_errors = @server_errors.dup
@@ -9,9 +9,9 @@ module Capybara
9
9
  # the comparison will depend on the :url option
10
10
  #
11
11
  # @!macro current_path_query_params
12
- # @overload $0(string, options = {})
12
+ # @overload $0(string, **options)
13
13
  # @param string [String] The string that the current 'path' should equal
14
- # @overload $0(regexp, options = {})
14
+ # @overload $0(regexp, **options)
15
15
  # @param regexp [Regexp] The regexp that the current 'path' should match to
16
16
  # @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
17
17
  # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
@@ -49,7 +49,7 @@ module Capybara
49
49
  def has_current_path?(path, **options)
50
50
  assert_current_path(path, options)
51
51
  rescue Capybara::ExpectationNotMet
52
- return false
52
+ false
53
53
  end
54
54
 
55
55
  ##
@@ -64,7 +64,7 @@ module Capybara
64
64
  def has_no_current_path?(path, **options)
65
65
  assert_no_current_path(path, options)
66
66
  rescue Capybara::ExpectationNotMet
67
- return false
67
+ false
68
68
  end
69
69
 
70
70
  private
@@ -74,7 +74,7 @@ module Capybara
74
74
  document.synchronize(query.wait) do
75
75
  yield(query)
76
76
  end
77
- return true
77
+ true
78
78
  end
79
79
  end
80
80
  end
@@ -76,7 +76,7 @@ Capybara::SpecHelper.spec '#assert_text' do
76
76
  @session.visit('/with_html')
77
77
  expect do
78
78
  @session.assert_text(/xxxxyzzz/)
79
- end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected to find text matching \/xxxxyzzz\/ in "This is a test\\nHeader Class(.+)"\Z/)
79
+ end.to raise_error(Capybara::ExpectationNotMet, %r{\Aexpected to find text matching /xxxxyzzz/ in "This is a test\\nHeader Class(.+)"\Z})
80
80
  end
81
81
 
82
82
  it "should escape any characters that would have special meaning in a regexp" do
@@ -7,19 +7,17 @@ Capybara::SpecHelper.spec '#body' do
7
7
  expect(@session.body).to include('Hello world!')
8
8
  end
9
9
 
10
- if "".respond_to?(:encoding)
11
- context "encoding of response between ascii and utf8" do
12
- it "should be valid with html entities" do
13
- @session.visit('/with_html_entities')
14
- expect(@session).to have_content('Encoding') # wait for content to appear if visit is async
15
- expect { @session.body.encode!("UTF-8") }.not_to raise_error
16
- end
10
+ context "encoding of response between ascii and utf8" do
11
+ it "should be valid with html entities" do
12
+ @session.visit('/with_html_entities')
13
+ expect(@session).to have_content('Encoding') # wait for content to appear if visit is async
14
+ expect { @session.body.encode!("UTF-8") }.not_to raise_error
15
+ end
17
16
 
18
- it "should be valid without html entities" do
19
- @session.visit('/with_html')
20
- expect(@session).to have_content('This is a test') # wait for content to appear if visit is async
21
- expect { @session.body.encode!("UTF-8") }.not_to raise_error
22
- end
17
+ it "should be valid without html entities" do
18
+ @session.visit('/with_html')
19
+ expect(@session).to have_content('This is a test') # wait for content to appear if visit is async
20
+ expect { @session.body.encode!("UTF-8") }.not_to raise_error
23
21
  end
24
22
  end
25
23
  end
@@ -93,12 +93,12 @@ Capybara::SpecHelper.spec '#click_link' do
93
93
  end
94
94
 
95
95
  it "should find a link matching an exact regex pattern" do
96
- @session.click_link('labore', href: /\/with_simple_html/)
96
+ @session.click_link('labore', href: %r{/with_simple_html})
97
97
  expect(@session).to have_content('Bar')
98
98
  end
99
99
 
100
100
  it "should find a link matching a partial regex pattern" do
101
- @session.click_link('labore', href: /\/with_simple/)
101
+ @session.click_link('labore', href: %r{/with_simple})
102
102
  expect(@session).to have_content('Bar')
103
103
  end
104
104
 
@@ -137,7 +137,7 @@ Capybara::SpecHelper.spec '#click_link' do
137
137
 
138
138
  it "should follow redirects back to itself" do
139
139
  @session.click_link('BackToMyself')
140
- expect(@session).to have_css('#referrer', text: /\/with_html$/)
140
+ expect(@session).to have_css('#referrer', text: %r{/with_html$})
141
141
  expect(@session).to have_content('This is a test')
142
142
  end
143
143
 
@@ -28,7 +28,7 @@ Capybara::SpecHelper.spec '#assert_matches_selector' do
28
28
  end
29
29
 
30
30
  it "should not accept count options" do
31
- expect { @element.assert_matches_selector(:css, '.number', count: 1) }.to raise_error(ArgumentError)
31
+ expect { @element.assert_matches_selector(:css, '.number', count: 1) }.to raise_error(ArgumentError, /count/)
32
32
  end
33
33
 
34
34
  it "should accept a filter block" do
@@ -159,9 +159,18 @@ Capybara::SpecHelper.spec "#fill_in" do
159
159
  expect(extract_results(@session)['zipcode']).to eq('12345')
160
160
  end
161
161
 
162
+ it "fills in a field if default_set_options is nil" do
163
+ Capybara.default_set_options = nil
164
+ @session.fill_in(:form_first_name, with: 'Thomas')
165
+ @session.click_button('awesome')
166
+ expect(extract_results(@session)['first_name']).to eq('Thomas')
167
+ end
168
+
162
169
  context 'on a pre-populated textfield with a reformatting onchange', requires: [:js] do
163
170
  it 'should only trigger onchange once' do
164
171
  @session.visit('/with_js')
172
+ # Click somewhere on the page to ensure focus is acquired. Without this FF won't generate change events for some reason???
173
+ @session.find(:css, 'body').click
165
174
  @session.fill_in('with_change_event', with: 'some value')
166
175
  # click outside the field to trigger the change event
167
176
  @session.find(:css, 'body').click
@@ -39,7 +39,7 @@ Capybara::SpecHelper.spec '#find_field' do
39
39
  it "should raise error if filter option is invalid" do
40
40
  expect do
41
41
  @session.find_field('Dog', disabled: nil)
42
- end.to raise_error ArgumentError, "Invalid value nil passed to filter disabled"
42
+ end.to raise_error ArgumentError, "Invalid value nil passed to NodeFilter disabled"
43
43
  end
44
44
 
45
45
  context "with :exact option" do
@@ -127,7 +127,8 @@ Capybara::SpecHelper.spec '#find' do
127
127
  before do
128
128
  Capybara.add_selector(:beatle) do
129
129
  xpath { |name| ".//li[contains(@class, 'beatle')][contains(text(), '#{name}')]" }
130
- filter(:type) { |node, type| node[:class].split(/\s+/).include?(type) }
130
+ node_filter(:type) { |node, type| node[:class].split(/\s+/).include?(type) }
131
+ node_filter(:fail) { |_node, _val| raise Capybara::ElementNotFound, 'fail' }
131
132
  end
132
133
  end
133
134
 
@@ -145,13 +146,17 @@ Capybara::SpecHelper.spec '#find' do
145
146
  expect { @session.find(:beatle, 'John', type: 'drummer') }.to raise_error(Capybara::ElementNotFound)
146
147
  expect { @session.find(:beatle, 'George', type: 'drummer') }.to raise_error(Capybara::ElementNotFound)
147
148
  end
149
+
150
+ it "should not raise an ElementNotFound error from in a filter" do
151
+ expect { @session.find(:beatle, 'John', fail: 'something') }.to raise_error(Capybara::ElementNotFound, /beatle "John"/)
152
+ end
148
153
  end
149
154
 
150
155
  context "with custom selector with custom filter and default" do
151
156
  before do
152
157
  Capybara.add_selector(:beatle) do
153
158
  xpath { |name| ".//li[contains(@class, 'beatle')][contains(text(), '#{name}')]" }
154
- filter(:type, default: "drummer") { |node, type| node[:class].split(/\s+/).include?(type) }
159
+ node_filter(:type, default: "drummer") { |node, type| node[:class].split(/\s+/).include?(type) }
155
160
  end
156
161
  end
157
162
 
@@ -174,7 +179,7 @@ Capybara::SpecHelper.spec '#find' do
174
179
  context "with alternate filter set" do
175
180
  before do
176
181
  Capybara::Selector::FilterSet.add(:value) do
177
- filter(:with) { |node, with| node.value == with.to_s }
182
+ node_filter(:with) { |node, with| node.value == with.to_s }
178
183
  end
179
184
 
180
185
  Capybara.add_selector(:id_with_field_filters) do
@@ -10,7 +10,7 @@ Capybara::SpecHelper.spec '#has_link?' do
10
10
  expect(@session).to have_link('awesome title')
11
11
  expect(@session).to have_link('A link', href: '/with_simple_html')
12
12
  expect(@session).to have_link(:'A link', href: :'/with_simple_html')
13
- expect(@session).to have_link('A link', href: /\/with_simple_html/)
13
+ expect(@session).to have_link('A link', href: %r{/with_simple_html})
14
14
  end
15
15
 
16
16
  it "should be false if the given link is not on the page" do
@@ -34,6 +34,6 @@ Capybara::SpecHelper.spec '#has_no_link?' do
34
34
  it "should be true if the given link is not on the page" do
35
35
  expect(@session).to have_no_link('monkey')
36
36
  expect(@session).to have_no_link('A link', href: '/nonexistent-href')
37
- expect(@session).to have_no_link('A link', href: /\/nonexistent-href/)
37
+ expect(@session).to have_no_link('A link', href: %r{/nonexistent-href})
38
38
  end
39
39
  end
@@ -102,6 +102,14 @@ Capybara::SpecHelper.spec "node" do
102
102
  expect { @session.first('//textarea[@readonly]').set('changed') }.to raise_error(Capybara::ReadOnlyElementError)
103
103
  end
104
104
 
105
+ it 'should use global default options' do
106
+ Capybara.default_set_options = { clear: :backspace }
107
+ element = @session.first(:fillable_field, type: 'text')
108
+ allow(element.base).to receive(:set)
109
+ element.set('gorilla')
110
+ expect(element.base).to have_received(:set).with('gorilla', clear: :backspace)
111
+ end
112
+
105
113
  context "with a contenteditable element", requires: [:js] do
106
114
  it 'should allow me to change the contents' do
107
115
  @session.visit('/with_js')
@@ -447,6 +455,48 @@ Capybara::SpecHelper.spec "node" do
447
455
  end
448
456
  end
449
457
 
458
+ describe "#execute_script", requires: %i[js es_args] do
459
+ it "should execute the given script in the context of the element and return nothing" do
460
+ @session.visit('/with_js')
461
+ expect(@session.find(:css, '#change').execute_script("this.textContent = 'Funky Doodle'")).to be_nil
462
+ expect(@session).to have_css('#change', text: 'Funky Doodle')
463
+ end
464
+
465
+ it "should pass arguments to the script" do
466
+ @session.visit('/with_js')
467
+ @session.find(:css, '#change').execute_script("this.textContent = arguments[0]", "Doodle Funk")
468
+ expect(@session).to have_css('#change', text: 'Doodle Funk')
469
+ end
470
+ end
471
+
472
+ describe "#evaluate_script", requires: %i[js es_args] do
473
+ it "should evaluate the given script in the context of the element and return whatever it produces" do
474
+ @session.visit('/with_js')
475
+ el = @session.find(:css, '#with_change_event')
476
+ expect(el.evaluate_script("this.value")).to eq('default value')
477
+ end
478
+
479
+ it "should pass arguments to the script" do
480
+ @session.visit('/with_js')
481
+ @session.find(:css, '#change').evaluate_script("this.textContent = arguments[0]", "Doodle Funk")
482
+ expect(@session).to have_css('#change', text: 'Doodle Funk')
483
+ end
484
+
485
+ it "should pass multiple arguments" do
486
+ @session.visit('/with_js')
487
+ change = @session.find(:css, '#change')
488
+ expect(change.evaluate_script("arguments[0] + arguments[1]", 2, 3)).to eq 5
489
+ end
490
+
491
+ it "should support returning elements" do
492
+ @session.visit('/with_js')
493
+ change = @session.find(:css, '#change') # ensure page has loaded and element is available
494
+ el = change.evaluate_script("this")
495
+ expect(el).to be_instance_of(Capybara::Node::Element)
496
+ expect(el).to eq(change)
497
+ end
498
+ end
499
+
450
500
  describe '#reload', requires: [:js] do
451
501
  context "without automatic reload" do
452
502
  before { Capybara.automatic_reload = false }