capybara 1.1.4 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +100 -0
  2. data/License.txt +22 -0
  3. data/README.md +829 -0
  4. data/lib/capybara.rb +124 -6
  5. data/lib/capybara/cucumber.rb +2 -5
  6. data/lib/capybara/driver/base.rb +5 -5
  7. data/lib/capybara/driver/node.rb +2 -2
  8. data/lib/capybara/dsl.rb +3 -121
  9. data/lib/capybara/node/actions.rb +12 -28
  10. data/lib/capybara/node/base.rb +5 -13
  11. data/lib/capybara/node/element.rb +21 -21
  12. data/lib/capybara/node/finders.rb +27 -89
  13. data/lib/capybara/node/matchers.rb +107 -69
  14. data/lib/capybara/node/simple.rb +11 -13
  15. data/lib/capybara/query.rb +78 -0
  16. data/lib/capybara/rack_test/browser.rb +16 -27
  17. data/lib/capybara/rack_test/driver.rb +11 -1
  18. data/lib/capybara/rack_test/node.rb +17 -1
  19. data/lib/capybara/result.rb +84 -0
  20. data/lib/capybara/rspec/matchers.rb +28 -63
  21. data/lib/capybara/selector.rb +97 -33
  22. data/lib/capybara/selenium/driver.rb +14 -61
  23. data/lib/capybara/selenium/node.rb +6 -15
  24. data/lib/capybara/server.rb +32 -27
  25. data/lib/capybara/session.rb +54 -30
  26. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  27. data/lib/capybara/spec/public/jquery.js +9046 -0
  28. data/lib/capybara/spec/public/test.js +4 -1
  29. data/lib/capybara/spec/session.rb +56 -27
  30. data/lib/capybara/spec/session/all_spec.rb +8 -4
  31. data/lib/capybara/spec/session/attach_file_spec.rb +12 -9
  32. data/lib/capybara/spec/session/check_spec.rb +6 -3
  33. data/lib/capybara/spec/session/choose_spec.rb +4 -1
  34. data/lib/capybara/spec/session/click_button_spec.rb +5 -14
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -1
  36. data/lib/capybara/spec/session/click_link_spec.rb +3 -17
  37. data/lib/capybara/spec/session/current_url_spec.rb +77 -9
  38. data/lib/capybara/spec/session/fill_in_spec.rb +8 -18
  39. data/lib/capybara/spec/session/find_spec.rb +19 -46
  40. data/lib/capybara/spec/session/first_spec.rb +2 -34
  41. data/lib/capybara/spec/session/has_css_spec.rb +1 -1
  42. data/lib/capybara/spec/session/has_field_spec.rb +28 -0
  43. data/lib/capybara/spec/session/has_select_spec.rb +84 -31
  44. data/lib/capybara/spec/session/has_table_spec.rb +7 -69
  45. data/lib/capybara/spec/session/has_text_spec.rb +168 -0
  46. data/lib/capybara/spec/session/javascript.rb +65 -81
  47. data/lib/capybara/spec/session/node_spec.rb +115 -0
  48. data/lib/capybara/spec/session/screenshot.rb +29 -0
  49. data/lib/capybara/spec/session/select_spec.rb +12 -12
  50. data/lib/capybara/spec/session/text_spec.rb +9 -4
  51. data/lib/capybara/spec/session/unselect_spec.rb +12 -6
  52. data/lib/capybara/spec/session/visit_spec.rb +76 -0
  53. data/lib/capybara/spec/session/within_frame_spec.rb +33 -0
  54. data/lib/capybara/spec/session/within_spec.rb +47 -58
  55. data/lib/capybara/spec/session/within_window_spec.rb +40 -0
  56. data/lib/capybara/spec/test_app.rb +27 -3
  57. data/lib/capybara/spec/views/form.erb +11 -10
  58. data/lib/capybara/spec/views/host_links.erb +2 -2
  59. data/lib/capybara/spec/views/tables.erb +6 -66
  60. data/lib/capybara/spec/views/with_html.erb +3 -3
  61. data/lib/capybara/spec/views/with_js.erb +11 -8
  62. data/lib/capybara/util/save_and_open_page.rb +4 -3
  63. data/lib/capybara/version.rb +1 -1
  64. data/spec/basic_node_spec.rb +15 -3
  65. data/spec/dsl_spec.rb +12 -10
  66. data/spec/rack_test_spec.rb +152 -0
  67. data/spec/rspec/features_spec.rb +0 -2
  68. data/spec/rspec/matchers_spec.rb +164 -89
  69. data/spec/rspec_spec.rb +0 -2
  70. data/spec/selenium_spec.rb +67 -0
  71. data/spec/server_spec.rb +35 -23
  72. data/spec/spec_helper.rb +18 -2
  73. metadata +30 -30
  74. data/README.rdoc +0 -722
  75. data/lib/capybara/spec/driver.rb +0 -301
  76. data/lib/capybara/spec/session/current_host_spec.rb +0 -68
  77. data/lib/capybara/spec/session/has_content_spec.rb +0 -106
  78. data/lib/capybara/util/timeout.rb +0 -27
  79. data/spec/driver/rack_test_driver_spec.rb +0 -89
  80. data/spec/driver/selenium_driver_spec.rb +0 -37
  81. data/spec/session/rack_test_session_spec.rb +0 -55
  82. data/spec/session/selenium_session_spec.rb +0 -26
  83. data/spec/string_spec.rb +0 -77
  84. data/spec/timeout_spec.rb +0 -28
@@ -1,13 +1,7 @@
1
1
  module Capybara
2
2
  class Selector
3
- attr_reader :name
3
+ attr_reader :name, :custom_filters
4
4
 
5
- class Normalized
6
- attr_accessor :selector, :locator, :options, :xpaths
7
-
8
- def failure_message; selector.failure_message; end
9
- def name; selector.name; end
10
- end
11
5
 
12
6
  class << self
13
7
  def all
@@ -21,32 +15,13 @@ module Capybara
21
15
  def remove(name)
22
16
  all.delete(name.to_sym)
23
17
  end
24
-
25
- def normalize(*args)
26
- normalized = Normalized.new
27
- normalized.options = if args.last.is_a?(Hash) then args.pop else {} end
28
-
29
- if args[1]
30
- normalized.selector = all[args[0]]
31
- normalized.locator = args[1]
32
- else
33
- normalized.selector = all.values.find { |s| s.match?(args[0]) }
34
- normalized.locator = args[0]
35
- end
36
- normalized.selector ||= all[Capybara.default_selector]
37
-
38
- xpath = normalized.selector.call(normalized.locator)
39
- if xpath.respond_to?(:to_xpaths)
40
- normalized.xpaths = xpath.to_xpaths
41
- else
42
- normalized.xpaths = [xpath.to_s].flatten
43
- end
44
- normalized
45
- end
46
18
  end
47
19
 
48
20
  def initialize(name, &block)
49
21
  @name = name
22
+ @custom_filters = {}
23
+ @match = nil
24
+ @failure_message = nil
50
25
  instance_eval(&block)
51
26
  end
52
27
 
@@ -55,14 +30,22 @@ module Capybara
55
30
  @xpath
56
31
  end
57
32
 
33
+ # Same as xpath, but wrap in XPath.css().
34
+ def css(&block)
35
+ if block
36
+ @xpath = xpath { |*args| XPath.css(block.call(*args)) }
37
+ end
38
+ @xpath
39
+ end
40
+
58
41
  def match(&block)
59
42
  @match = block if block
60
43
  @match
61
44
  end
62
45
 
63
- def failure_message(&block)
64
- @failure_message = block if block
65
- @failure_message
46
+ def label(label=nil)
47
+ @label = label if label
48
+ @label
66
49
  end
67
50
 
68
51
  def call(locator)
@@ -72,6 +55,10 @@ module Capybara
72
55
  def match?(locator)
73
56
  @match and @match.call(locator)
74
57
  end
58
+
59
+ def filter(name, &block)
60
+ @custom_filters[name] = block
61
+ end
75
62
  end
76
63
  end
77
64
 
@@ -80,10 +67,87 @@ Capybara.add_selector(:xpath) do
80
67
  end
81
68
 
82
69
  Capybara.add_selector(:css) do
83
- xpath { |css| XPath.css(css) }
70
+ css { |css| css }
84
71
  end
85
72
 
86
73
  Capybara.add_selector(:id) do
87
74
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
88
75
  match { |value| value.is_a?(Symbol) }
89
76
  end
77
+
78
+ Capybara.add_selector(:field) do
79
+ xpath { |locator| XPath::HTML.field(locator) }
80
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
81
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
82
+ filter(:with) { |node, with| node.value == with }
83
+ filter(:type) { |node, type| node[:type] == type }
84
+ end
85
+
86
+ Capybara.add_selector(:fieldset) do
87
+ xpath { |locator| XPath::HTML.fieldset(locator) }
88
+ end
89
+
90
+ Capybara.add_selector(:link_or_button) do
91
+ label "link or button"
92
+ xpath { |locator| XPath::HTML.link_or_button(locator) }
93
+ end
94
+
95
+ Capybara.add_selector(:link) do
96
+ xpath { |locator| XPath::HTML.link(locator) }
97
+ filter(:href) do |node, href|
98
+ node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href)])
99
+ end
100
+ end
101
+
102
+ Capybara.add_selector(:button) do
103
+ xpath { |locator| XPath::HTML.button(locator) }
104
+ end
105
+
106
+ Capybara.add_selector(:fillable_field) do
107
+ label "field"
108
+ xpath { |locator| XPath::HTML.fillable_field(locator) }
109
+ end
110
+
111
+ Capybara.add_selector(:radio_button) do
112
+ label "radio button"
113
+ xpath { |locator| XPath::HTML.radio_button(locator) }
114
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
115
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
116
+ end
117
+
118
+ Capybara.add_selector(:checkbox) do
119
+ xpath { |locator| XPath::HTML.checkbox(locator) }
120
+ filter(:checked) { |node, value| not(value ^ node.checked?) }
121
+ filter(:unchecked) { |node, value| (value ^ node.checked?) }
122
+ end
123
+
124
+ Capybara.add_selector(:select) do
125
+ label "select box"
126
+ xpath { |locator| XPath::HTML.select(locator) }
127
+ filter(:options) do |node, options|
128
+ actual = node.all(:xpath, './/option').map { |option| option.text }
129
+ options.sort == actual.sort
130
+ end
131
+ filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option) } }
132
+ filter(:selected) do |node, selected|
133
+ actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
134
+ [selected].flatten.sort == actual.sort
135
+ end
136
+ end
137
+
138
+ Capybara.add_selector(:option) do
139
+ xpath { |locator| XPath::HTML.option(locator) }
140
+ end
141
+
142
+ Capybara.add_selector(:file_field) do
143
+ label "file field"
144
+ xpath { |locator| XPath::HTML.file_field(locator) }
145
+ end
146
+
147
+ Capybara.add_selector(:content) do
148
+ xpath { |content| XPath::HTML.content(content) }
149
+ end
150
+
151
+ Capybara.add_selector(:table) do
152
+ xpath { |locator| XPath::HTML.table(locator) }
153
+ end
@@ -2,13 +2,11 @@ require 'selenium-webdriver'
2
2
 
3
3
  class Capybara::Selenium::Driver < Capybara::Driver::Base
4
4
  DEFAULT_OPTIONS = {
5
- :resynchronize => false,
6
- :resynchronization_timeout => 10,
7
5
  :browser => :firefox
8
6
  }
9
- SPECIAL_OPTIONS = [:browser, :resynchronize, :resynchronization_timeout]
7
+ SPECIAL_OPTIONS = [:browser]
10
8
 
11
- attr_reader :app, :rack_server, :options
9
+ attr_reader :app, :options
12
10
 
13
11
  def browser
14
12
  unless @browser
@@ -27,13 +25,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
27
25
 
28
26
  def initialize(app, options={})
29
27
  @app = app
28
+ @browser = nil
29
+ @exit_status = nil
30
30
  @options = DEFAULT_OPTIONS.merge(options)
31
- @rack_server = Capybara::Server.new(@app)
32
- @rack_server.boot if Capybara.run_server
33
31
  end
34
32
 
35
33
  def visit(path)
36
- browser.navigate.to(url(path))
34
+ browser.navigate.to(path)
37
35
  end
38
36
 
39
37
  def source
@@ -53,18 +51,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
53
51
  end
54
52
 
55
53
  def wait?; true; end
56
-
57
- def resynchronize
58
- if options[:resynchronize]
59
- load_wait_for_ajax_support
60
- yield
61
- Capybara.timeout(options[:resynchronization_timeout], self, "failed to resynchronize, ajax request timed out") do
62
- evaluate_script("!window.capybaraRequestsOutstanding")
63
- end
64
- else
65
- yield
66
- end
67
- end
54
+ def needs_server?; true; end
68
55
 
69
56
  def execute_script(script)
70
57
  browser.execute_script script
@@ -74,12 +61,15 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
74
61
  browser.execute_script "return #{script}"
75
62
  end
76
63
 
64
+ def save_screenshot(path, options={})
65
+ browser.save_screenshot(path)
66
+ end
67
+
77
68
  def reset!
78
69
  # Use instance variable directly so we avoid starting the browser just to reset the session
79
70
  if @browser
80
- begin
81
- @browser.manage.delete_all_cookies
82
- rescue Selenium::WebDriver::Error::UnhandledError => e
71
+ begin @browser.manage.delete_all_cookies
72
+ rescue Selenium::WebDriver::Error::UnhandledError
83
73
  # delete_all_cookies fails when we've previously gone
84
74
  # to about:blank, so we rescue this error and do nothing
85
75
  # instead.
@@ -92,6 +82,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
92
82
  old_window = browser.window_handle
93
83
  browser.switch_to.frame(frame_id)
94
84
  yield
85
+ ensure
95
86
  browser.switch_to.window old_window
96
87
  end
97
88
 
@@ -122,44 +113,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
122
113
  end
123
114
 
124
115
  def invalid_element_errors
125
- [ Selenium::WebDriver::Error::StaleElementReferenceError,
126
- Selenium::WebDriver::Error::InvalidSelectorError,
127
- Selenium::WebDriver::Error::UnknownError ]
128
- end
129
-
130
- private
131
-
132
- def load_wait_for_ajax_support
133
- browser.execute_script <<-JS
134
- window.capybaraRequestsOutstanding = 0;
135
- (function() { // Overriding XMLHttpRequest
136
- var oldXHR = window.XMLHttpRequest;
137
-
138
- function newXHR() {
139
- var realXHR = new oldXHR();
140
-
141
- window.capybaraRequestsOutstanding++;
142
- realXHR.addEventListener("readystatechange", function() {
143
- if( realXHR.readyState == 4 ) {
144
- setTimeout( function() {
145
- window.capybaraRequestsOutstanding--;
146
- if(window.capybaraRequestsOutstanding < 0) {
147
- window.capybaraRequestsOutstanding = 0;
148
- }
149
- }, 500 );
150
- }
151
- }, false);
152
-
153
- return realXHR;
154
- }
155
-
156
- window.XMLHttpRequest = newXHR;
157
- })();
158
- JS
159
- end
160
-
161
- def url(path)
162
- rack_server.url(path)
116
+ [Selenium::WebDriver::Error::ObsoleteElementError, Selenium::WebDriver::Error::UnhandledError]
163
117
  end
164
-
165
118
  end
@@ -23,34 +23,29 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
23
23
  elsif tag_name == 'input' and type == 'checkbox'
24
24
  click if value ^ native.attribute('checked').to_s.eql?("true")
25
25
  elsif tag_name == 'input' and type == 'file'
26
- resynchronize do
27
- native.send_keys(value.to_s)
28
- end
26
+ native.send_keys(value.to_s)
29
27
  elsif tag_name == 'textarea' or tag_name == 'input'
30
- resynchronize do
31
- native.clear
32
- native.send_keys(value.to_s)
33
- end
28
+ native.send_keys(("\b" * native[:value].size) + value.to_s)
34
29
  end
35
30
  end
36
31
 
37
32
  def select_option
38
- resynchronize { native.click } unless selected?
33
+ native.click unless selected?
39
34
  end
40
35
 
41
36
  def unselect_option
42
37
  if select_node['multiple'] != 'multiple' and select_node['multiple'] != 'true'
43
38
  raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
44
39
  end
45
- resynchronize { native.click } if selected?
40
+ native.click if selected?
46
41
  end
47
42
 
48
43
  def click
49
- resynchronize { native.click }
44
+ native.click
50
45
  end
51
46
 
52
47
  def drag_to(element)
53
- resynchronize { driver.browser.action.drag_and_drop(native, element.native).perform }
48
+ driver.browser.action.drag_and_drop(native, element.native).perform
54
49
  end
55
50
 
56
51
  def tag_name
@@ -75,10 +70,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
75
70
 
76
71
  private
77
72
 
78
- def resynchronize
79
- driver.resynchronize { yield }
80
- end
81
-
82
73
  # a reference to the select node if this is an option node
83
74
  def select_node
84
75
  find('./ancestor::select').first
@@ -1,11 +1,12 @@
1
1
  require 'uri'
2
2
  require 'net/http'
3
3
  require 'rack'
4
- require 'capybara/util/timeout'
5
4
 
6
5
  module Capybara
7
6
  class Server
8
- class Identify
7
+ class Middleware
8
+ attr_accessor :error
9
+
9
10
  def initialize(app)
10
11
  @app = app
11
12
  end
@@ -14,7 +15,12 @@ module Capybara
14
15
  if env["PATH_INFO"] == "/__identify__"
15
16
  [200, {}, [@app.object_id.to_s]]
16
17
  else
17
- @app.call(env)
18
+ begin
19
+ @app.call(env)
20
+ rescue StandardError => e
21
+ @error = e unless @error
22
+ raise e
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -27,23 +33,30 @@ module Capybara
27
33
 
28
34
  attr_reader :app, :port
29
35
 
30
- def initialize(app)
36
+ def initialize(app, port=Capybara.server_port)
31
37
  @app = app
38
+ @middleware = Middleware.new(@app)
39
+ @server_thread = nil # supress warnings
40
+ @port = port
41
+ @port ||= Capybara::Server.ports[@app.object_id]
42
+ @port ||= find_available_port
32
43
  end
33
44
 
34
- def host
35
- "127.0.0.1"
45
+ def reset_error!
46
+ @middleware.error = nil
36
47
  end
37
48
 
38
- def url(path)
39
- if path =~ /^http/
40
- path
41
- else
42
- (Capybara.app_host || "http://#{host}:#{port}") + path.to_s
43
- end
49
+ def error
50
+ @middleware.error
51
+ end
52
+
53
+ def host
54
+ Capybara.server_host || "127.0.0.1"
44
55
  end
45
56
 
46
57
  def responsive?
58
+ return false if @server_thread && @server_thread.join(0)
59
+
47
60
  res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
48
61
 
49
62
  if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
@@ -54,25 +67,17 @@ module Capybara
54
67
  end
55
68
 
56
69
  def boot
57
- if @app
58
- @port = Capybara::Server.ports[@app.object_id]
59
-
60
- if not @port or not responsive?
61
- @port = Capybara.server_port || find_available_port
62
- Capybara::Server.ports[@app.object_id] = @port
63
-
64
- Thread.new do
65
- Capybara.server.call(Identify.new(@app), @port)
66
- end
70
+ unless responsive?
71
+ Capybara::Server.ports[@app.object_id] = @port
67
72
 
68
- Capybara.timeout(Capybara.server_boot_timeout) do
69
- if responsive? then true else sleep(0.5) and false end
70
- end
73
+ @server_thread = Thread.new do
74
+ Capybara.server.call(@middleware, @port)
71
75
  end
76
+
77
+ Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
72
78
  end
73
79
  rescue TimeoutError
74
- puts "Rack application timed out during boot"
75
- exit
80
+ raise "Rack application timed out during boot"
76
81
  else
77
82
  self
78
83
  end