webrat 0.2.0 → 0.3.0

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 (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