bjeanes-capybara 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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