capybara 0.1.3 → 0.1.4

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.
@@ -1,10 +1,6 @@
1
1
  require 'capybara'
2
2
  require 'capybara/dsl'
3
3
 
4
- require 'database_cleaner'
5
- require 'database_cleaner/cucumber'
6
- DatabaseCleaner.strategy = :truncation
7
-
8
4
  Capybara.app = Rack::Builder.new do
9
5
  map "/" do
10
6
  use Rails::Rack::Static
@@ -1,5 +1,7 @@
1
+ require 'uri'
1
2
  require 'net/http'
2
3
  require 'rack'
4
+ require 'rack/handler/mongrel'
3
5
 
4
6
  class Capybara::Server
5
7
  attr_reader :app
@@ -17,6 +19,7 @@ class Capybara::Server
17
19
  end
18
20
 
19
21
  def url(path)
22
+ path = URI.parse(path).request_uri if path =~ /^http/
20
23
  "http://#{host}:#{port}#{path}"
21
24
  end
22
25
 
@@ -1,26 +1,6 @@
1
1
  module Capybara
2
-
3
- class << self
4
- attr_writer :default_selector
5
-
6
- def default_selector
7
- @default_selector ||= :xpath
8
- end
9
- end
10
-
11
2
  class Session
12
3
 
13
- FIELDS_PATHS = {
14
- :text_field => proc { |id| "//input[@type='text'][@id='#{id}']" },
15
- :text_area => proc { |id| "//textarea[@id='#{id}']" },
16
- :password_field => proc { |id| "//input[@type='password'][@id='#{id}']" },
17
- :radio => proc { |id| "//input[@type='radio'][@id='#{id}']" },
18
- :hidden_field => proc { |id| "//input[@type='hidden'][@id='#{id}']" },
19
- :checkbox => proc { |id| "//input[@type='checkbox'][@id='#{id}']" },
20
- :select => proc { |id| "//select[@id='#{id}']" },
21
- :file_field => proc { |id| "//input[@type='file'][@id='#{id}']" }
22
- }
23
-
24
4
  attr_reader :mode, :app
25
5
 
26
6
  def initialize(mode, app)
@@ -46,35 +26,59 @@ module Capybara
46
26
  end
47
27
 
48
28
  def click_link(locator)
49
- find_link(locator).click
29
+ link = wait_for(XPath.link(locator))
30
+ raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link
31
+ link.click
50
32
  end
51
33
 
52
34
  def click_button(locator)
53
- find_button(locator).click
35
+ button = wait_for(XPath.button(locator))
36
+ raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button
37
+ button.click
38
+ end
39
+
40
+ def drag(source_locator, target_locator)
41
+ source = find(source_locator)
42
+ raise Capybara::ElementNotFound, "drag source '#{source_locator}' not found on page" unless source
43
+ target = find(target_locator)
44
+ raise Capybara::ElementNotFound, "drag target '#{target_locator}' not found on page" unless target
45
+ source.drag_to(target)
54
46
  end
55
47
 
56
48
  def fill_in(locator, options={})
57
- find_field(locator, :text_field, :text_area, :password_field).set(options[:with])
49
+ field = wait_for(XPath.fillable_field(locator))
50
+ raise Capybara::ElementNotFound, "cannot fill in, no text field, text area or password field with id or label '#{locator}' found" unless field
51
+ field.set(options[:with])
58
52
  end
59
53
 
60
54
  def choose(locator)
61
- find_field(locator, :radio).set(true)
55
+ field = wait_for(XPath.radio_button(locator))
56
+ raise Capybara::ElementNotFound, "cannot choose field, no radio button with id or label '#{locator}' found" unless field
57
+ field.set(true)
62
58
  end
63
59
 
64
60
  def check(locator)
65
- find_field(locator, :checkbox).set(true)
61
+ field = wait_for(XPath.checkbox(locator))
62
+ raise Capybara::ElementNotFound, "cannot check field, no checkbox with id or label '#{locator}' found" unless field
63
+ field.set(true)
66
64
  end
67
65
 
68
66
  def uncheck(locator)
69
- find_field(locator, :checkbox).set(false)
67
+ field = wait_for(XPath.checkbox(locator))
68
+ raise Capybara::ElementNotFound, "cannot uncheck field, no checkbox with id or label '#{locator}' found" unless field
69
+ field.set(false)
70
70
  end
71
71
 
72
72
  def select(value, options={})
73
- find_field(options[:from], :select).select(value)
73
+ field = wait_for(XPath.select(options[:from]))
74
+ raise Capybara::ElementNotFound, "cannot select option, no select box with id or label '#{options[:from]}' found" unless field
75
+ field.select(value)
74
76
  end
75
77
 
76
78
  def attach_file(locator, path)
77
- find_field(locator, :file_field).set(path)
79
+ field = wait_for(XPath.file_field(locator))
80
+ raise Capybara::ElementNotFound, "cannot attach file, no file field with id or label '#{locator}' found" unless field
81
+ field.set(path)
78
82
  end
79
83
 
80
84
  def body
@@ -82,11 +86,11 @@ module Capybara
82
86
  end
83
87
 
84
88
  def has_content?(content)
85
- has_xpath?("//*[contains(.,#{sanitized_xpath_string(content)})]")
89
+ has_xpath?(XPath.content(content).to_s)
86
90
  end
87
91
 
88
92
  def has_xpath?(path, options={})
89
- results = find(path)
93
+ results = all(path)
90
94
  if options[:text]
91
95
  results = filter_by_text(results, options[:text])
92
96
  end
@@ -104,20 +108,20 @@ module Capybara
104
108
  def within(kind, scope=nil)
105
109
  kind, scope = Capybara.default_selector, kind unless scope
106
110
  scope = css_to_xpath(scope) if kind == :css
107
- raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" if find(scope).empty?
111
+ raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless wait_for(scope)
108
112
  scopes.push(scope)
109
113
  yield
110
114
  scopes.pop
111
115
  end
112
116
 
113
117
  def within_fieldset(locator)
114
- within "//fieldset[@id='#{locator}' or contains(legend,'#{locator}')]" do
118
+ within XPath.fieldset(locator) do
115
119
  yield
116
120
  end
117
121
  end
118
122
 
119
123
  def within_table(locator)
120
- within "//table[@id='#{locator}' or contains(caption,'#{locator}')]" do
124
+ within XPath.table(locator) do
121
125
  yield
122
126
  end
123
127
  end
@@ -127,25 +131,45 @@ module Capybara
127
131
  Capybara::SaveAndOpenPage.save_and_open_page(body)
128
132
  end
129
133
 
130
- def find_field(locator, *kinds)
131
- kinds = FIELDS_PATHS.keys if kinds.empty?
132
- field = find_field_by_id(locator, *kinds) || find_field_by_label(locator, *kinds)
133
- raise Capybara::ElementNotFound, "no field of kind #{kinds.inspect} with id or label '#{locator}' found" unless field
134
- field
134
+ def all(locator)
135
+ XPath.wrap(locator).scope(current_scope).paths.map do |path|
136
+ driver.find(path)
137
+ end.flatten
138
+ end
139
+
140
+ def find(locator)
141
+ all(locator).first
142
+ end
143
+
144
+ def wait_for(locator)
145
+ return find(locator) unless driver.wait?
146
+ 8.times do
147
+ result = find(locator)
148
+ return result if result
149
+ sleep(0.1)
150
+ end
151
+ nil
152
+ end
153
+
154
+ def find_field(locator)
155
+ find(XPath.field(locator))
135
156
  end
136
157
  alias_method :field_labeled, :find_field
137
158
 
138
159
  def find_link(locator)
139
- link = find("//a[@id='#{locator}' or contains(.,'#{locator}') or @title='#{locator}']").first
140
- raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link
141
- link
160
+ find(XPath.link(locator))
142
161
  end
143
162
 
144
163
  def find_button(locator)
145
- button = find("//input[@type='submit' or @type='image'][@id='#{locator}' or @value='#{locator}']").first \
146
- || find("//button[@id='#{locator}' or @value='#{locator}' or contains(.,'#{locator}')]").first
147
- raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button
148
- button
164
+ find(XPath.button(locator))
165
+ end
166
+
167
+ def evaluate_script(script)
168
+ begin
169
+ driver.evaluate_script(script)
170
+ rescue NoMethodError
171
+ raise NotSupportedByDriverError
172
+ end
149
173
  end
150
174
 
151
175
  private
@@ -172,41 +196,5 @@ module Capybara
172
196
  def scopes
173
197
  @scopes ||= []
174
198
  end
175
-
176
- def find_field_by_id(locator, *kinds)
177
- kinds.each do |kind|
178
- path = FIELDS_PATHS[kind]
179
- element = find(path.call(locator)).first
180
- return element if element
181
- end
182
- return nil
183
- end
184
-
185
- def find_field_by_label(locator, *kinds)
186
- kinds.each do |kind|
187
- label = find("//label[contains(.,'#{locator}')]").first
188
- if label
189
- element = find_field_by_id(label[:for], kind)
190
- return element if element
191
- end
192
- end
193
- return nil
194
- end
195
-
196
- def find(locator)
197
- locator = current_scope.to_s + locator
198
- driver.find(locator)
199
- end
200
-
201
- def sanitized_xpath_string(string)
202
- if string =~ /'/
203
- string = string.split("'", -1).map do |substr|
204
- "'#{substr}'"
205
- end.join(%q{,"'",})
206
- "concat(#{string})"
207
- else
208
- "'#{string}'"
209
- end
210
- end
211
199
  end
212
200
  end
@@ -0,0 +1,124 @@
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
+ class << self
7
+ def wrap(path)
8
+ if path.is_a?(self)
9
+ path
10
+ else
11
+ new(path.to_s)
12
+ end
13
+ end
14
+
15
+ def respond_to?(method)
16
+ new.respond_to?(method)
17
+ end
18
+
19
+ def method_missing(*args)
20
+ new.send(*args)
21
+ end
22
+ end
23
+
24
+ attr_reader :paths
25
+
26
+ def initialize(*paths)
27
+ @paths = paths
28
+ end
29
+
30
+ def field(locator)
31
+ fillable_field(locator).file_field(locator).checkbox(locator).radio_button(locator).select(locator)
32
+ end
33
+
34
+ def fillable_field(locator)
35
+ text_field(locator).password_field(locator).text_area(locator)
36
+ end
37
+
38
+ def content(locator)
39
+ append("/descendant-or-self::*[contains(.,#{s(locator)})]")
40
+ end
41
+
42
+ def table(locator)
43
+ append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]")
44
+ end
45
+
46
+ def fieldset(locator)
47
+ append("//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
48
+ end
49
+
50
+ def link(locator)
51
+ append("//a[@id=#{s(locator)} or contains(.,#{s(locator)}) or @title=#{s(locator)}]")
52
+ end
53
+
54
+ def button(locator)
55
+ xpath = append("//input[@type='submit' or @type='image'][@id=#{s(locator)} or @value=#{s(locator)}]")
56
+ xpath.append("//button[@id=#{s(locator)} or @value=#{s(locator)} or contains(.,#{s(locator)})]")
57
+ end
58
+
59
+ def text_field(locator)
60
+ add_field(locator) { |id| "//input[@type='text'][@id=#{id}]" }
61
+ end
62
+
63
+ def password_field(locator)
64
+ add_field(locator) { |id| "//input[@type='password'][@id=#{id}]" }
65
+ end
66
+
67
+ def text_area(locator)
68
+ add_field(locator) { |id| "//textarea[@id=#{id}]" }
69
+ end
70
+
71
+ def radio_button(locator)
72
+ add_field(locator) { |id| "//input[@type='radio'][@id=#{id}]" }
73
+ end
74
+
75
+ def checkbox(locator)
76
+ add_field(locator) { |id| "//input[@type='checkbox'][@id=#{id}]" }
77
+ end
78
+
79
+ def select(locator)
80
+ add_field(locator) { |id| "//select[@id=#{id}]" }
81
+ end
82
+
83
+ def file_field(locator)
84
+ add_field(locator) { |id| "//input[@type='file'][@id=#{id}]" }
85
+ end
86
+
87
+ def scope(scope)
88
+ XPath.new(*paths.map { |p| scope + p })
89
+ end
90
+
91
+ def to_s
92
+ @paths.join(' | ')
93
+ end
94
+
95
+ protected
96
+
97
+ def add_field(locator)
98
+ xpath = append(yield(s(locator)))
99
+ xpath = xpath.append(yield("//label[contains(.,#{s(locator)})]/@for"))
100
+ xpath.prepend(yield("//label[text()=#{s(locator)}]/@for"))
101
+ end
102
+
103
+ # Sanitize a String for putting it into an xpath query
104
+ def s(string)
105
+ if string.include?("'")
106
+ string = string.split("'", -1).map do |substr|
107
+ "'#{substr}'"
108
+ end.join(%q{,"'",})
109
+ "concat(#{string})"
110
+ else
111
+ "'#{string}'"
112
+ end
113
+ end
114
+
115
+ def prepend(path)
116
+ XPath.new(*[path, @paths].flatten)
117
+ end
118
+
119
+ def append(path)
120
+ XPath.new(*[@paths, path].flatten)
121
+ end
122
+
123
+ end
124
+ end
@@ -4,7 +4,7 @@ describe Capybara::Driver::Selenium do
4
4
  before do
5
5
  @driver = Capybara::Driver::Selenium.new(TestApp)
6
6
  end
7
-
7
+
8
8
  it_should_behave_like "driver"
9
9
  it_should_behave_like "driver with javascript support"
10
10
  end
@@ -37,7 +37,7 @@ shared_examples_for 'driver' do
37
37
  @driver.find('//a')[0].text.should == 'labore'
38
38
  @driver.find('//a')[1].text.should == 'ullamco'
39
39
  end
40
-
40
+
41
41
  it "should extract node attributes" do
42
42
  @driver.find('//a')[0][:href].should == '/with_simple_html'
43
43
  @driver.find('//a')[0][:class].should == 'simple'
@@ -51,7 +51,7 @@ shared_examples_for 'driver' do
51
51
  @driver.find('//input').first.set('gorilla')
52
52
  @driver.find('//input').first.value.should == 'gorilla'
53
53
  end
54
-
54
+
55
55
  it "should extract node tag name" do
56
56
  @driver.find('//a')[0].tag_name.should == 'a'
57
57
  @driver.find('//a')[1].tag_name.should == 'a'
@@ -63,10 +63,27 @@ shared_examples_for 'driver' do
63
63
  end
64
64
 
65
65
  shared_examples_for "driver with javascript support" do
66
+ before { @driver.visit('/with_js') }
67
+
66
68
  describe '#find' do
67
69
  it "should find dynamically changed nodes" do
68
- @driver.visit('/with_js')
69
70
  @driver.find('//p').first.text.should == 'I changed it'
70
71
  end
71
72
  end
73
+
74
+ describe '#drag_to' do
75
+ it "should drag and drop an object" do
76
+ draggable = @driver.find('//div[@id="drag"]').first
77
+ droppable = @driver.find('//div[@id="drop"]').first
78
+ draggable.drag_to(droppable)
79
+ @driver.find('//div[contains(., "Dropped!")]').should_not be_nil
80
+ end
81
+ end
82
+
83
+ describe "#evaluate_script" do
84
+ it "should return the value of the executed script" do
85
+ @driver.evaluate_script('1+1').should == 2
86
+ end
87
+ end
88
+
72
89
  end
@@ -36,7 +36,7 @@ describe Capybara do
36
36
  Capybara.current_driver.should == :culerity
37
37
  end
38
38
  end
39
-
39
+
40
40
  describe '#javascript_driver' do
41
41
  it "should default to selenium" do
42
42
  Capybara.javascript_driver.should == :selenium
@@ -98,7 +98,7 @@ describe Capybara do
98
98
  Capybara.current_session.app.should == Capybara.app
99
99
  end
100
100
  end
101
-
101
+
102
102
  describe '.reset_sessions!' do
103
103
  it "should clear any persisted sessions" do
104
104
  object_id = Capybara.current_session.object_id
@@ -114,6 +114,7 @@ describe Capybara do
114
114
  end
115
115
 
116
116
  it_should_behave_like "session"
117
+ it_should_behave_like "session without javascript support"
117
118
 
118
119
  it "should be possible to include it in another class" do
119
120
  klass = Class.new do
@@ -124,7 +125,7 @@ describe Capybara do
124
125
  foo.click_link('ullamco')
125
126
  foo.body.should include('Another World')
126
127
  end
127
-
128
+
128
129
  it "should provide a 'page' shortcut for more expressive tests" do
129
130
  klass = Class.new do
130
131
  include Capybara