capybara 0.1.3 → 0.1.4

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