bjeanes-capybara 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +87 -0
  3. data/README.rdoc +404 -0
  4. data/Rakefile +29 -0
  5. data/config.ru +6 -0
  6. data/lib/capybara.rb +53 -0
  7. data/lib/capybara/cucumber.rb +32 -0
  8. data/lib/capybara/driver/base.rb +37 -0
  9. data/lib/capybara/driver/celerity_driver.rb +135 -0
  10. data/lib/capybara/driver/culerity_driver.rb +25 -0
  11. data/lib/capybara/driver/rack_test_driver.rb +244 -0
  12. data/lib/capybara/driver/selenium_driver.rb +137 -0
  13. data/lib/capybara/dsl.rb +60 -0
  14. data/lib/capybara/node.rb +69 -0
  15. data/lib/capybara/rails.rb +11 -0
  16. data/lib/capybara/save_and_open_page.rb +33 -0
  17. data/lib/capybara/searchable.rb +53 -0
  18. data/lib/capybara/server.rb +111 -0
  19. data/lib/capybara/session.rb +274 -0
  20. data/lib/capybara/wait_until.rb +23 -0
  21. data/lib/capybara/xpath.rb +176 -0
  22. data/script/console +10 -0
  23. data/script/destroy +14 -0
  24. data/script/generate +14 -0
  25. data/spec/capybara_spec.rb +18 -0
  26. data/spec/driver/celerity_driver_spec.rb +17 -0
  27. data/spec/driver/culerity_driver_spec.rb +13 -0
  28. data/spec/driver/rack_test_driver_spec.rb +12 -0
  29. data/spec/driver/remote_culerity_driver_spec.rb +26 -0
  30. data/spec/driver/remote_selenium_driver_spec.rb +18 -0
  31. data/spec/driver/selenium_driver_spec.rb +12 -0
  32. data/spec/drivers_spec.rb +139 -0
  33. data/spec/dsl/all_spec.rb +69 -0
  34. data/spec/dsl/attach_file_spec.rb +64 -0
  35. data/spec/dsl/check_spec.rb +39 -0
  36. data/spec/dsl/choose_spec.rb +26 -0
  37. data/spec/dsl/click_button_spec.rb +218 -0
  38. data/spec/dsl/click_link_spec.rb +108 -0
  39. data/spec/dsl/click_spec.rb +24 -0
  40. data/spec/dsl/current_url_spec.rb +8 -0
  41. data/spec/dsl/fill_in_spec.rb +91 -0
  42. data/spec/dsl/find_button_spec.rb +16 -0
  43. data/spec/dsl/find_by_id_spec.rb +16 -0
  44. data/spec/dsl/find_field_spec.rb +22 -0
  45. data/spec/dsl/find_link_spec.rb +17 -0
  46. data/spec/dsl/find_spec.rb +57 -0
  47. data/spec/dsl/has_button_spec.rb +32 -0
  48. data/spec/dsl/has_content_spec.rb +101 -0
  49. data/spec/dsl/has_css_spec.rb +107 -0
  50. data/spec/dsl/has_field_spec.rb +96 -0
  51. data/spec/dsl/has_link_spec.rb +33 -0
  52. data/spec/dsl/has_xpath_spec.rb +123 -0
  53. data/spec/dsl/locate_spec.rb +59 -0
  54. data/spec/dsl/select_spec.rb +71 -0
  55. data/spec/dsl/uncheck_spec.rb +21 -0
  56. data/spec/dsl/within_spec.rb +153 -0
  57. data/spec/dsl_spec.rb +140 -0
  58. data/spec/fixtures/capybara.jpg +0 -0
  59. data/spec/fixtures/test_file.txt +1 -0
  60. data/spec/public/jquery-ui.js +35 -0
  61. data/spec/public/jquery.js +19 -0
  62. data/spec/public/test.js +30 -0
  63. data/spec/save_and_open_page_spec.rb +43 -0
  64. data/spec/searchable_spec.rb +61 -0
  65. data/spec/server_spec.rb +47 -0
  66. data/spec/session/celerity_session_spec.rb +27 -0
  67. data/spec/session/culerity_session_spec.rb +25 -0
  68. data/spec/session/rack_test_session_spec.rb +25 -0
  69. data/spec/session/selenium_session_spec.rb +25 -0
  70. data/spec/session_spec.rb +77 -0
  71. data/spec/session_with_headers_support_spec.rb +13 -0
  72. data/spec/session_with_javascript_support_spec.rb +182 -0
  73. data/spec/session_without_headers_support_spec.rb +15 -0
  74. data/spec/session_without_javascript_support_spec.rb +15 -0
  75. data/spec/spec_helper.rb +27 -0
  76. data/spec/test_app.rb +71 -0
  77. data/spec/views/buttons.erb +4 -0
  78. data/spec/views/fieldsets.erb +29 -0
  79. data/spec/views/form.erb +226 -0
  80. data/spec/views/postback.erb +13 -0
  81. data/spec/views/tables.erb +122 -0
  82. data/spec/views/with_html.erb +38 -0
  83. data/spec/views/with_js.erb +34 -0
  84. data/spec/views/with_scope.erb +36 -0
  85. data/spec/views/with_simple_html.erb +1 -0
  86. data/spec/wait_until_spec.rb +28 -0
  87. data/spec/xpath_spec.rb +271 -0
  88. metadata +239 -0
@@ -0,0 +1,274 @@
1
+ require 'capybara/wait_until'
2
+
3
+ module Capybara
4
+ class Session
5
+ include Searchable
6
+
7
+ DSL_METHODS = [
8
+ :all, :attach_file, :body, :check, :choose, :click, :click_button, :click_link, :current_url, :drag, :evaluate_script,
9
+ :field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
10
+ :has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
11
+ :visit, :wait_until, :within, :within_fieldset, :within_table, :has_link?, :has_no_link?, :has_button?, :has_no_button?,
12
+ :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?
13
+ ]
14
+
15
+ attr_reader :mode, :app
16
+
17
+ def initialize(mode, app)
18
+ @mode = mode
19
+ @app = app
20
+ end
21
+
22
+ def driver
23
+ @driver ||= case mode
24
+ when :rack_test
25
+ Capybara::Driver::RackTest.new(app)
26
+ when :selenium
27
+ Capybara::Driver::Selenium.new(app)
28
+ when :celerity
29
+ Capybara::Driver::Celerity.new(app)
30
+ when :culerity
31
+ Capybara::Driver::Culerity.new(app)
32
+ else
33
+ raise Capybara::DriverNotFoundError, "no driver called #{mode} was found"
34
+ end
35
+ end
36
+
37
+ def cleanup!
38
+ driver.cleanup!
39
+ end
40
+
41
+ def current_url
42
+ driver.current_url
43
+ end
44
+
45
+ def response_headers
46
+ driver.response_headers
47
+ end
48
+
49
+ def visit(path)
50
+ driver.visit(path)
51
+ end
52
+
53
+ def click(locator)
54
+ msg = "no link or button '#{locator}' found"
55
+ locate(:xpath, XPath.link(locator).button(locator), msg).click
56
+ end
57
+
58
+ def click_link(locator)
59
+ msg = "no link with title, id or text '#{locator}' found"
60
+ locate(:xpath, XPath.link(locator), msg).click
61
+ end
62
+
63
+ def click_button(locator)
64
+ msg = "no button with value or id or text '#{locator}' found"
65
+ locate(:xpath, XPath.button(locator), msg).click
66
+ end
67
+
68
+ def drag(source_locator, target_locator)
69
+ source = locate(:xpath, source_locator, "drag source '#{source_locator}' not found on page")
70
+ target = locate(:xpath, target_locator, "drag target '#{target_locator}' not found on page")
71
+ source.drag_to(target)
72
+ end
73
+
74
+ def fill_in(locator, options={})
75
+ msg = "cannot fill in, no text field, text area or password field with id, name, or label '#{locator}' found"
76
+ locate(:xpath, XPath.fillable_field(locator), msg).set(options[:with])
77
+ end
78
+
79
+ def choose(locator)
80
+ msg = "cannot choose field, no radio button with id, name, or label '#{locator}' found"
81
+ locate(:xpath, XPath.radio_button(locator), msg).set(true)
82
+ end
83
+
84
+ def check(locator)
85
+ msg = "cannot check field, no checkbox with id, name, or label '#{locator}' found"
86
+ locate(:xpath, XPath.checkbox(locator), msg).set(true)
87
+ end
88
+
89
+ def uncheck(locator)
90
+ msg = "cannot uncheck field, no checkbox with id, name, or label '#{locator}' found"
91
+ locate(:xpath, XPath.checkbox(locator), msg).set(false)
92
+ end
93
+
94
+ def select(value, options={})
95
+ msg = "cannot select option, no select box with id, name, or label '#{options[:from]}' found"
96
+ locate(:xpath, XPath.select(options[:from]), msg).select(value)
97
+ end
98
+
99
+ def unselect(value, options={})
100
+ msg = "cannot unselect option, no select box with id, name, or label '#{options[:from]}' found"
101
+ locate(:xpath, XPath.select(options[:from]), msg).unselect(value)
102
+ end
103
+
104
+ def attach_file(locator, path)
105
+ msg = "cannot attach file, no file field with id, name, or label '#{locator}' found"
106
+ locate(:xpath, XPath.file_field(locator), msg).set(path)
107
+ end
108
+
109
+ def body
110
+ driver.body
111
+ end
112
+
113
+ def source
114
+ driver.source
115
+ end
116
+
117
+ def within(kind, scope=nil)
118
+ kind, scope = Capybara.default_selector, kind unless scope
119
+ scope = XPath.from_css(scope) if kind == :css
120
+ locate(:xpath, scope, "scope '#{scope}' not found on page")
121
+ begin
122
+ scopes.push(scope)
123
+ yield
124
+ ensure
125
+ scopes.pop
126
+ end
127
+ end
128
+
129
+ def within_fieldset(locator)
130
+ within :xpath, XPath.fieldset(locator) do
131
+ yield
132
+ end
133
+ end
134
+
135
+ def within_table(locator)
136
+ within :xpath, XPath.table(locator) do
137
+ yield
138
+ end
139
+ end
140
+
141
+ def has_xpath?(path, options={})
142
+ wait_conditionally_until do
143
+ results = all(:xpath, path, options)
144
+
145
+ if options[:count]
146
+ results.size == options[:count]
147
+ else
148
+ results.size > 0
149
+ end
150
+ end
151
+ rescue Capybara::TimeoutError
152
+ return false
153
+ end
154
+
155
+ def has_no_xpath?(path, options={})
156
+ wait_conditionally_until do
157
+ results = all(:xpath, path, options)
158
+
159
+ if options[:count]
160
+ results.size != options[:count]
161
+ else
162
+ results.empty?
163
+ end
164
+ end
165
+ rescue Capybara::TimeoutError
166
+ return false
167
+ end
168
+
169
+ def has_css?(path, options={})
170
+ has_xpath?(XPath.from_css(path), options)
171
+ end
172
+
173
+ def has_no_css?(path, options={})
174
+ has_no_xpath?(XPath.from_css(path), options)
175
+ end
176
+
177
+ def has_content?(content)
178
+ has_xpath?(XPath.content(content))
179
+ end
180
+
181
+ def has_no_content?(content)
182
+ has_no_xpath?(XPath.content(content))
183
+ end
184
+
185
+ def has_link?(locator)
186
+ has_xpath?(XPath.link(locator))
187
+ end
188
+
189
+ def has_no_link?(locator)
190
+ has_no_xpath?(XPath.link(locator))
191
+ end
192
+
193
+ def has_button?(locator)
194
+ has_xpath?(XPath.button(locator))
195
+ end
196
+
197
+ def has_no_button?(locator)
198
+ has_no_xpath?(XPath.button(locator))
199
+ end
200
+
201
+ def has_field?(locator, options={})
202
+ has_xpath?(XPath.field(locator, options))
203
+ end
204
+
205
+ def has_no_field?(locator, options={})
206
+ has_no_xpath?(XPath.field(locator, options))
207
+ end
208
+
209
+ def has_checked_field?(locator)
210
+ has_xpath?(XPath.field(locator, :checked => true))
211
+ end
212
+
213
+ def has_unchecked_field?(locator)
214
+ has_xpath?(XPath.field(locator, :unchecked => true))
215
+ end
216
+
217
+ def has_select?(locator, options={})
218
+ has_xpath?(XPath.select(locator, options))
219
+ end
220
+
221
+ def has_no_select?(locator, options={})
222
+ has_no_xpath?(XPath.select(locator, options))
223
+ end
224
+
225
+ def has_table?(locator, options={})
226
+ has_xpath?(XPath.table(locator, options))
227
+ end
228
+
229
+ def has_no_table?(locator, options={})
230
+ has_no_xpath?(XPath.table(locator, options))
231
+ end
232
+
233
+ def save_and_open_page
234
+ require 'capybara/save_and_open_page'
235
+ Capybara::SaveAndOpenPage.save_and_open_page(body)
236
+ end
237
+
238
+ #return node identified by locator or raise ElementNotFound(using desc)
239
+ def locate(kind_or_locator, locator=nil, fail_msg = nil)
240
+ node = wait_conditionally_until { find(kind_or_locator, locator) }
241
+ ensure
242
+ raise Capybara::ElementNotFound, fail_msg || "Unable to locate '#{kind_or_locator}'" unless node
243
+ return node
244
+ end
245
+
246
+ def wait_until(timeout = Capybara.default_wait_time)
247
+ WaitUntil.timeout(timeout) { yield }
248
+ end
249
+
250
+ def evaluate_script(script)
251
+ driver.evaluate_script(script)
252
+ end
253
+
254
+ private
255
+
256
+ def wait_conditionally_until
257
+ if driver.wait? then wait_until { yield } else yield end
258
+ end
259
+
260
+ def all_unfiltered(locator)
261
+ XPath.wrap(locator).scope(current_scope).paths.map do |path|
262
+ driver.find(path)
263
+ end.flatten
264
+ end
265
+
266
+ def current_scope
267
+ scopes.join('')
268
+ end
269
+
270
+ def scopes
271
+ @scopes ||= []
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,23 @@
1
+ module Capybara
2
+ #Provides timeout similar to standard library Timeout, but avoids threads
3
+ class WaitUntil
4
+
5
+ class << self
6
+
7
+ def timeout(seconds = 1, &block)
8
+ start_time = Time.now
9
+
10
+ result = nil
11
+
12
+ until result
13
+ return result if result = yield
14
+
15
+ if (Time.now - start_time) > seconds
16
+ raise TimeoutError
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,176 @@
1
+ module Capybara
2
+ # this is a class for generating XPath queries, use it like this:
3
+ # Xpath.text_field('foo').link('blah').to_s
4
+ # this will generate an XPath that matches either a text field or a link
5
+ class XPath
6
+
7
+ class << self
8
+ def from_css(css)
9
+ Nokogiri::CSS.xpath_for(css).first
10
+ end
11
+ alias_method :for_css, :from_css
12
+
13
+ def wrap(path)
14
+ if path.is_a?(self)
15
+ path
16
+ else
17
+ new(path.to_s)
18
+ end
19
+ end
20
+
21
+ def respond_to?(method)
22
+ new.respond_to?(method)
23
+ end
24
+
25
+ def method_missing(*args)
26
+ new.send(*args)
27
+ end
28
+ end
29
+
30
+ attr_reader :paths
31
+
32
+ def initialize(*paths)
33
+ @paths = paths
34
+ end
35
+
36
+ def field(locator, options={})
37
+ if options[:with]
38
+ fillable_field(locator, options)
39
+ else
40
+ xpath = fillable_field(locator)
41
+ xpath = xpath.input_field(:file, locator, options)
42
+ xpath = xpath.checkbox(locator, options)
43
+ xpath = xpath.radio_button(locator, options)
44
+ xpath.select(locator, options)
45
+ end
46
+ end
47
+
48
+ def fillable_field(locator, options={})
49
+ [:text, :password, :email, :url, :search, :tel, :color].inject(text_area(locator, options)) do |all, type|
50
+ all.input_field(type, locator, options)
51
+ end
52
+ end
53
+
54
+ def content(locator)
55
+ append("/descendant-or-self::*[contains(.,#{s(locator)})]")
56
+ end
57
+
58
+ def table(locator, options={})
59
+ conditions = ""
60
+ if options[:rows]
61
+ row_conditions = options[:rows].map do |row|
62
+ row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
63
+ "tr[./#{row}]"
64
+ end.join(sibling)
65
+ conditions << "[.//#{row_conditions}]"
66
+ end
67
+ append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
68
+ end
69
+
70
+ def fieldset(locator)
71
+ append("//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
72
+ end
73
+
74
+ def link(locator)
75
+ xpath = append("//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
76
+ xpath.prepend("//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
77
+ end
78
+
79
+ def button(locator)
80
+ xpath = append("//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
81
+ xpath = xpath.append("//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
82
+ xpath = xpath.prepend("//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
83
+ xpath = xpath.prepend("//button[@value=#{s(locator)} or text()=#{s(locator)}]")
84
+ end
85
+
86
+ def text_area(locator, options={})
87
+ options = options.merge(:text => options[:with]) if options.has_key?(:with)
88
+ add_field(locator, "//textarea", options)
89
+ end
90
+
91
+ def select(locator, options={})
92
+ add_field(locator, "//select", options)
93
+ end
94
+
95
+ def input_field(type, locator, options={})
96
+ options = options.merge(:value => options[:with]) if options.has_key?(:with)
97
+ add_field(locator, "//input[@type='#{type}']", options)
98
+ end
99
+
100
+ def scope(scope)
101
+ XPath.new(*paths.map { |p| scope + p })
102
+ end
103
+
104
+ def to_s
105
+ @paths.join(' | ')
106
+ end
107
+
108
+ def append(path)
109
+ XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
110
+ end
111
+
112
+ def prepend(path)
113
+ XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
114
+ end
115
+
116
+ def checkbox(locator, options={})
117
+ input_field(:checkbox, locator, options)
118
+ end
119
+
120
+ def radio_button(locator, options={})
121
+ input_field(:radio, locator, options)
122
+ end
123
+
124
+ [:text, :password, :email, :url, :search, :tel, :color, :file].each do |type|
125
+ class_eval <<-RUBY, __FILE__, __LINE__+1
126
+ def #{type}_field(locator)
127
+ input_field(:#{type}, locator)
128
+ end
129
+ RUBY
130
+ end
131
+
132
+ protected
133
+
134
+ # place this between to nodes to indicate that they should be siblings
135
+ def sibling
136
+ '/following-sibling::*[1]/self::'
137
+ end
138
+
139
+ def add_field(locator, field, options={})
140
+ postfix = extract_postfix(options)
141
+ xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
142
+ xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
143
+ xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
144
+ xpath = xpath.append("//label[contains(.,#{s(locator)})]#{field}#{postfix}")
145
+ xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
146
+ end
147
+
148
+ def extract_postfix(options)
149
+ options.inject("") do |postfix, (key, value)|
150
+ case key
151
+ when :value then postfix += "[@value=#{s(value)}]"
152
+ when :text then postfix += "[text()=#{s(value)}]"
153
+ when :checked then postfix += "[@checked]"
154
+ when :unchecked then postfix += "[not(@checked)]"
155
+ when :options then postfix += value.map { |o| "[./option/text()=#{s(o)}]" }.join
156
+ when :selected then postfix += [value].flatten.map { |o| "[./option[@selected]/text()=#{s(o)}]" }.join
157
+ end
158
+ postfix
159
+ end
160
+ end
161
+
162
+ # Sanitize a String for putting it into an xpath query
163
+ def s(string)
164
+ if string.include?("'")
165
+ string = string.split("'", -1).map do |substr|
166
+ "'#{substr}'"
167
+ end.join(%q{,"'",})
168
+ "concat(#{string})"
169
+ else
170
+ "'#{string}'"
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+ end