webrat 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/History.txt +76 -14
  2. data/README.txt +40 -36
  3. data/Rakefile +80 -18
  4. data/TODO.txt +9 -3
  5. data/init.rb +1 -1
  6. data/lib/webrat.rb +30 -5
  7. data/lib/webrat/core.rb +12 -0
  8. data/lib/webrat/core/area.rb +44 -0
  9. data/lib/webrat/core/field.rb +332 -0
  10. data/lib/webrat/core/flunk.rb +7 -0
  11. data/lib/webrat/core/form.rb +130 -0
  12. data/lib/webrat/core/label.rb +18 -0
  13. data/lib/webrat/core/link.rb +101 -0
  14. data/lib/webrat/core/locators.rb +92 -0
  15. data/lib/webrat/core/logging.rb +25 -0
  16. data/lib/webrat/core/matchers.rb +4 -0
  17. data/lib/webrat/core/matchers/have_content.rb +94 -0
  18. data/lib/webrat/core/matchers/have_selector.rb +39 -0
  19. data/lib/webrat/core/matchers/have_tag.rb +58 -0
  20. data/lib/webrat/core/matchers/have_xpath.rb +85 -0
  21. data/lib/webrat/core/methods.rb +44 -0
  22. data/lib/webrat/core/mime.rb +29 -0
  23. data/lib/webrat/core/nokogiri.rb +42 -0
  24. data/lib/webrat/core/scope.rb +208 -0
  25. data/lib/webrat/core/select_option.rb +29 -0
  26. data/lib/webrat/core/session.rb +188 -0
  27. data/lib/webrat/core_extensions/blank.rb +58 -0
  28. data/lib/webrat/core_extensions/deprecate.rb +8 -0
  29. data/lib/webrat/core_extensions/detect_mapped.rb +12 -0
  30. data/lib/webrat/core_extensions/hash_with_indifferent_access.rb +131 -0
  31. data/lib/webrat/core_extensions/meta_class.rb +6 -0
  32. data/lib/webrat/core_extensions/nil_to_param.rb +5 -0
  33. data/lib/webrat/mechanize.rb +28 -0
  34. data/lib/webrat/merb.rb +75 -0
  35. data/lib/webrat/rack.rb +24 -0
  36. data/lib/webrat/rails.rb +102 -0
  37. data/lib/webrat/rails/redirect_actions.rb +18 -0
  38. data/lib/webrat/selenium.rb +3 -0
  39. data/lib/webrat/selenium/location_strategy_javascript/button.js +12 -0
  40. data/lib/webrat/selenium/location_strategy_javascript/label.js +16 -0
  41. data/lib/webrat/selenium/location_strategy_javascript/webrat.js +5 -0
  42. data/lib/webrat/selenium/location_strategy_javascript/webratlink.js +9 -0
  43. data/lib/webrat/selenium/location_strategy_javascript/webratlinkwithin.js +15 -0
  44. data/lib/webrat/selenium/location_strategy_javascript/webratselectwithoption.js +5 -0
  45. data/lib/webrat/selenium/selenium_extensions.js +6 -0
  46. data/lib/webrat/selenium/selenium_session.rb +137 -0
  47. data/lib/webrat/sinatra.rb +19 -0
  48. metadata +66 -52
  49. data/Manifest.txt +0 -20
  50. data/lib/webrat/rails_extensions.rb +0 -27
  51. data/lib/webrat/session.rb +0 -523
  52. data/test/checks_test.rb +0 -121
  53. data/test/chooses_test.rb +0 -74
  54. data/test/clicks_button_test.rb +0 -308
  55. data/test/clicks_link_test.rb +0 -193
  56. data/test/fills_in_test.rb +0 -139
  57. data/test/helper.rb +0 -21
  58. data/test/reloads_test.rb +0 -26
  59. data/test/selects_test.rb +0 -93
  60. data/test/visits_test.rb +0 -31
@@ -0,0 +1,44 @@
1
+ module Webrat
2
+ module Methods #:nodoc:
3
+
4
+ def self.delegate_to_session(*meths)
5
+ meths.each do |meth|
6
+ self.class_eval <<-RUBY
7
+ def #{meth}(*args, &blk)
8
+ @session ||= ::Webrat::MerbSession.new
9
+ @session.#{meth}(*args, &blk)
10
+ end
11
+ RUBY
12
+ end
13
+ end
14
+
15
+ # all of these methods delegate to the @session, which should
16
+ # be created transparently.
17
+ #
18
+ # Note that when using Webrat, #request also uses @session, so
19
+ # that #request and webrat native functions behave interchangably
20
+
21
+ delegate_to_session \
22
+ :visits, :visit,
23
+ :within,
24
+ :header, :http_accept, :basic_auth,
25
+ :save_and_open_page,
26
+ :fill_in,
27
+ :check,
28
+ :uncheck,
29
+ :choose,
30
+ :select,
31
+ :attach_file,
32
+ :cookies,
33
+ :response,
34
+ :current_page,
35
+ :current_url,
36
+ :click_link,
37
+ :click_area,
38
+ :click_button,
39
+ :reload, :reloads,
40
+ :clicks_link_within,
41
+ :field_labeled
42
+
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ module Webrat
2
+ module MIME
3
+
4
+ def self.mime_type(string_or_symbol)
5
+ if string_or_symbol.is_a?(String)
6
+ string_or_symbol
7
+ else
8
+ case string_or_symbol
9
+ when :text then "text/plain"
10
+ when :html then "text/html"
11
+ when :js then "text/javascript"
12
+ when :css then "text/css"
13
+ when :ics then "text/calendar"
14
+ when :csv then "text/csv"
15
+ when :xml then "application/xml"
16
+ when :rss then "application/rss+xml"
17
+ when :atom then "application/atom+xml"
18
+ when :yaml then "application/x-yaml"
19
+ when :multipart_form then "multipart/form-data"
20
+ when :url_encoded_form then "application/x-www-form-urlencoded"
21
+ when :json then "application/json"
22
+ else
23
+ raise ArgumentError.new("Invalid Mime type: #{string_or_symbol.inspect}")
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ require "webrat/core_extensions/meta_class"
2
+
3
+ module Webrat
4
+
5
+ def self.nokogiri_document(stringlike) #:nodoc:
6
+ return stringlike.dom if stringlike.respond_to?(:dom)
7
+
8
+ if stringlike === Nokogiri::HTML::Document || stringlike === Nokogiri::XML::NodeSet
9
+ stringlike
10
+ elsif stringlike === StringIO
11
+ Nokogiri::HTML(stringlike.string)
12
+ elsif stringlike.respond_to?(:body)
13
+ Nokogiri::HTML(stringlike.body.to_s)
14
+ else
15
+ Nokogiri::HTML(stringlike.to_s)
16
+ end
17
+ end
18
+
19
+ def self.define_dom_method(object, dom) #:nodoc:
20
+ object.meta_class.send(:define_method, :dom) do
21
+ dom
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+
28
+ module Nokogiri
29
+ module CSS
30
+ class XPathVisitor
31
+
32
+ def visit_pseudo_class_text(node) #:nodoc:
33
+ "@type='text'"
34
+ end
35
+
36
+ def visit_pseudo_class_password(node) #:nodoc:
37
+ "@type='password'"
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,208 @@
1
+ require "nokogiri"
2
+ require "webrat/core/form"
3
+ require "webrat/core/locators"
4
+
5
+ module Webrat
6
+ class Scope
7
+ include Logging
8
+ include Flunk
9
+ include Locators
10
+
11
+ def self.from_page(session, response, response_body) #:nodoc:
12
+ new(session) do
13
+ @response = response
14
+ @response_body = response_body
15
+ end
16
+ end
17
+
18
+ def self.from_scope(session, scope, selector) #:nodoc:
19
+ new(session) do
20
+ @scope = scope
21
+ @selector = selector
22
+ end
23
+ end
24
+
25
+ def initialize(session, &block) #:nodoc:
26
+ @session = session
27
+ instance_eval(&block) if block_given?
28
+ end
29
+
30
+ # Verifies an input field or textarea exists on the current page, and stores a value for
31
+ # it which will be sent when the form is submitted.
32
+ #
33
+ # Examples:
34
+ # fill_in "Email", :with => "user@example.com"
35
+ # fill_in "user[email]", :with => "user@example.com"
36
+ #
37
+ # The field value is required, and must be specified in <tt>options[:with]</tt>.
38
+ # <tt>field</tt> can be either the value of a name attribute (i.e. <tt>user[email]</tt>)
39
+ # or the text inside a <tt><label></tt> element that points at the <tt><input></tt> field.
40
+ def fill_in(field_locator, options = {})
41
+ field = locate_field(field_locator, TextField, TextareaField, PasswordField)
42
+ field.raise_error_if_disabled
43
+ field.set(options[:with])
44
+ end
45
+
46
+ alias_method :fills_in, :fill_in
47
+
48
+ # Verifies that an input checkbox exists on the current page and marks it
49
+ # as checked, so that the value will be submitted with the form.
50
+ #
51
+ # Example:
52
+ # check 'Remember Me'
53
+ def check(field_locator)
54
+ locate_field(field_locator, CheckboxField).check
55
+ end
56
+
57
+ alias_method :checks, :check
58
+
59
+ # Verifies that an input checkbox exists on the current page and marks it
60
+ # as unchecked, so that the value will not be submitted with the form.
61
+ #
62
+ # Example:
63
+ # uncheck 'Remember Me'
64
+ def uncheck(field_locator)
65
+ locate_field(field_locator, CheckboxField).uncheck
66
+ end
67
+
68
+ alias_method :unchecks, :uncheck
69
+
70
+ # Verifies that an input radio button exists on the current page and marks it
71
+ # as checked, so that the value will be submitted with the form.
72
+ #
73
+ # Example:
74
+ # choose 'First Option'
75
+ def choose(field_locator)
76
+ locate_field(field_locator, RadioField).choose
77
+ end
78
+
79
+ alias_method :chooses, :choose
80
+
81
+ # Verifies that a an option element exists on the current page with the specified
82
+ # text. You can optionally restrict the search to a specific select list by
83
+ # assigning <tt>options[:from]</tt> the value of the select list's name or
84
+ # a label. Stores the option's value to be sent when the form is submitted.
85
+ #
86
+ # Examples:
87
+ # selects "January"
88
+ # selects "February", :from => "event_month"
89
+ # selects "February", :from => "Event Month"
90
+ def selects(option_text, options = {})
91
+ find_select_option(option_text, options[:from]).choose
92
+ end
93
+
94
+ alias_method :select, :selects
95
+
96
+ # Verifies that an input file field exists on the current page and sets
97
+ # its value to the given +file+, so that the file will be uploaded
98
+ # along with the form. An optional <tt>content_type</tt> may be given.
99
+ #
100
+ # Example:
101
+ # attaches_file "Resume", "/path/to/the/resume.txt"
102
+ # attaches_file "Photo", "/path/to/the/image.png", "image/png"
103
+ def attach_file(field_locator, path, content_type = nil)
104
+ locate_field(field_locator, FileField).set(path, content_type)
105
+ end
106
+
107
+ alias_method :attaches_file, :attach_file
108
+
109
+ def click_area(area_name)
110
+ find_area(area_name).click
111
+ end
112
+
113
+ alias_method :clicks_area, :click_area
114
+
115
+ # Issues a request for the URL pointed to by a link on the current page,
116
+ # follows any redirects, and verifies the final page load was successful.
117
+ #
118
+ # click_link has very basic support for detecting Rails-generated
119
+ # JavaScript onclick handlers for PUT, POST and DELETE links, as well as
120
+ # CSRF authenticity tokens if they are present.
121
+ #
122
+ # Javascript imitation can be disabled by passing the option :javascript => false
123
+ #
124
+ # Passing a :method in the options hash overrides the HTTP method used
125
+ # for making the link request
126
+ #
127
+ # Example:
128
+ # click_link "Sign up"
129
+ #
130
+ # click_link "Sign up", :javascript => false
131
+ #
132
+ # click_link "Sign up", :method => :put
133
+ def click_link(link_text, options = {})
134
+ find_link(link_text).click(options)
135
+ end
136
+
137
+ alias_method :clicks_link, :click_link
138
+
139
+ # Verifies that a submit button exists for the form, then submits the form, follows
140
+ # any redirects, and verifies the final page was successful.
141
+ #
142
+ # Example:
143
+ # click_button "Login"
144
+ # click_button
145
+ #
146
+ # The URL and HTTP method for the form submission are automatically read from the
147
+ # <tt>action</tt> and <tt>method</tt> attributes of the <tt><form></tt> element.
148
+ def click_button(value = nil)
149
+ find_button(value).click
150
+ end
151
+
152
+ alias_method :clicks_button, :click_button
153
+
154
+ def dom # :nodoc:
155
+ return @dom if @dom
156
+
157
+ if @selector
158
+ @dom = scoped_dom
159
+ else
160
+ @dom = page_dom
161
+ end
162
+
163
+ return @dom
164
+ end
165
+
166
+ protected
167
+
168
+ def page_dom #:nodoc:
169
+ return @response.dom if @response.respond_to?(:dom)
170
+ dom = Webrat.nokogiri_document(@response_body)
171
+ Webrat.define_dom_method(@response, dom)
172
+ return dom
173
+ end
174
+
175
+ def scoped_dom #:nodoc:
176
+ Webrat.nokogiri_document(@scope.dom.search(@selector).first.to_html)
177
+ end
178
+
179
+ def locate_field(field_locator, *field_types) #:nodoc:
180
+ if field_locator.is_a?(Field)
181
+ field_locator
182
+ else
183
+ field(field_locator, *field_types)
184
+ end
185
+ end
186
+
187
+ def areas #:nodoc:
188
+ dom.search("area").map do |element|
189
+ Area.new(@session, element)
190
+ end
191
+ end
192
+
193
+ def links #:nodoc:
194
+ dom.search("a[@href]").map do |link_element|
195
+ Link.new(@session, link_element)
196
+ end
197
+ end
198
+
199
+ def forms #:nodoc:
200
+ return @forms if @forms
201
+
202
+ @forms = dom.search("form").map do |form_element|
203
+ Form.new(@session, form_element)
204
+ end
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,29 @@
1
+ module Webrat
2
+ class SelectOption #:nodoc:
3
+
4
+ def initialize(select, element)
5
+ @select = select
6
+ @element = element
7
+ end
8
+
9
+ def matches_text?(text)
10
+ if text.is_a?(Regexp)
11
+ @element.inner_html =~ text
12
+ else
13
+ @element.inner_html == text.to_s
14
+ end
15
+ end
16
+
17
+ def choose
18
+ @select.raise_error_if_disabled
19
+ @select.set(value)
20
+ end
21
+
22
+ protected
23
+
24
+ def value
25
+ @element["value"] || @element.inner_html
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,188 @@
1
+ require "forwardable"
2
+ require "ostruct"
3
+
4
+ require "webrat/core/mime"
5
+
6
+ module Webrat
7
+ class Session
8
+ extend Forwardable
9
+ include Logging
10
+ include Flunk
11
+
12
+ attr_reader :current_url
13
+
14
+ def initialize #:nodoc:
15
+ @http_method = :get
16
+ @data = {}
17
+ @default_headers = {}
18
+ @custom_headers = {}
19
+ end
20
+
21
+ # Saves the page out to RAILS_ROOT/tmp/ and opens it in the default
22
+ # web browser if on OS X. Useful for debugging.
23
+ #
24
+ # Example:
25
+ # save_and_open_page
26
+ def save_and_open_page
27
+ return unless File.exist?(saved_page_dir)
28
+
29
+ filename = "#{saved_page_dir}/webrat-#{Time.now.to_i}.html"
30
+
31
+ File.open(filename, "w") do |f|
32
+ f.write rewrite_css_and_image_references(response_body)
33
+ end
34
+
35
+ open_in_browser(filename)
36
+ end
37
+
38
+ def current_dom #:nodoc:
39
+ current_scope.dom
40
+ end
41
+
42
+ # For backwards compatibility -- removing in 1.0
43
+ def current_page #:nodoc:
44
+ page = OpenStruct.new
45
+ page.url = @current_url
46
+ page.http_method = @http_method
47
+ page.data = @data
48
+ page
49
+ end
50
+
51
+ def doc_root #:nodoc:
52
+ nil
53
+ end
54
+
55
+ def saved_page_dir #:nodoc:
56
+ File.expand_path(".")
57
+ end
58
+
59
+ def header(key, value)
60
+ @custom_headers[key] = value
61
+ end
62
+
63
+ def http_accept(mime_type)
64
+ header('Accept', Webrat::MIME.mime_type(mime_type))
65
+ end
66
+
67
+ def basic_auth(user, pass)
68
+ encoded_login = ["#{user}:#{pass}"].pack("m*")
69
+ header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
70
+ end
71
+
72
+ def headers #:nodoc:
73
+ @default_headers.dup.merge(@custom_headers.dup)
74
+ end
75
+
76
+ def request_page(url, http_method, data) #:nodoc:
77
+ h = headers
78
+ h['HTTP_REFERER'] = @current_url if @current_url
79
+
80
+ debug_log "REQUESTING PAGE: #{http_method.to_s.upcase} #{url} with #{data.inspect} and HTTP headers #{h.inspect}"
81
+ if h.empty?
82
+ send "#{http_method}", url, data || {}
83
+ else
84
+ send "#{http_method}", url, data || {}, h
85
+ end
86
+
87
+ save_and_open_page if exception_caught?
88
+ flunk("Page load was not successful (Code: #{response_code.inspect}):\n#{formatted_error}") unless success_code?
89
+
90
+ @_scopes = nil
91
+ @_page_scope = nil
92
+ @current_url = url
93
+ @http_method = http_method
94
+ @data = data
95
+
96
+ return response
97
+ end
98
+
99
+ def success_code? #:nodoc:
100
+ (200..499).include?(response_code)
101
+ end
102
+
103
+ def exception_caught? #:nodoc:
104
+ response_body =~ /Exception caught/
105
+ end
106
+
107
+ def current_scope #:nodoc:
108
+ scopes.last || page_scope
109
+ end
110
+
111
+ # Reloads the last page requested. Note that this will resubmit forms
112
+ # and their data.
113
+ #
114
+ # Example:
115
+ # reloads
116
+ def reloads
117
+ request_page(@current_url, @http_method, @data)
118
+ end
119
+
120
+ alias_method :reload, :reloads
121
+
122
+
123
+ # Works like click_link, but only looks for the link text within a given selector
124
+ #
125
+ # Example:
126
+ # click_link_within "#user_12", "Vote"
127
+ def click_link_within(selector, link_text)
128
+ within(selector) do
129
+ click_link(link_text)
130
+ end
131
+ end
132
+
133
+ alias_method :clicks_link_within, :click_link_within
134
+
135
+ def within(selector)
136
+ scopes.push(Scope.from_scope(self, current_scope, selector))
137
+ ret = yield(current_scope)
138
+ scopes.pop
139
+ return ret
140
+ end
141
+
142
+ # Issues a GET request for a page, follows any redirects, and verifies the final page
143
+ # load was successful.
144
+ #
145
+ # Example:
146
+ # visit "/"
147
+ def visit(url = nil, http_method = :get, data = {})
148
+ request_page(url, http_method, data)
149
+ end
150
+
151
+ alias_method :visits, :visit
152
+
153
+ def open_in_browser(path) #:nodoc
154
+ `open #{path}`
155
+ end
156
+
157
+ def rewrite_css_and_image_references(response_html) #:nodoc
158
+ return response_html unless doc_root
159
+ response_html.gsub(/"\/(stylesheets|images)/, doc_root + '/\1')
160
+ end
161
+
162
+ # Subclasses can override this to show error messages without html
163
+ def formatted_error #:nodoc:
164
+ response_body
165
+ end
166
+
167
+ def scopes #:nodoc:
168
+ @_scopes ||= []
169
+ end
170
+
171
+ def page_scope #:nodoc:
172
+ @_page_scope ||= Scope.from_page(self, response, response_body)
173
+ end
174
+
175
+ def_delegators :current_scope, :fill_in, :fills_in
176
+ def_delegators :current_scope, :check, :checks
177
+ def_delegators :current_scope, :uncheck, :unchecks
178
+ def_delegators :current_scope, :choose, :chooses
179
+ def_delegators :current_scope, :select, :selects
180
+ def_delegators :current_scope, :attach_file, :attaches_file
181
+ def_delegators :current_scope, :click_area, :clicks_area
182
+ def_delegators :current_scope, :click_link, :clicks_link
183
+ def_delegators :current_scope, :click_button, :clicks_button
184
+ def_delegators :current_scope, :should_see
185
+ def_delegators :current_scope, :should_not_see
186
+ def_delegators :current_scope, :field_labeled
187
+ end
188
+ end