oki-celerity 0.8.1.dev
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.
- data/HISTORY +111 -0
- data/LICENSE +621 -0
- data/README.rdoc +80 -0
- data/Rakefile +11 -0
- data/VERSION.yml +5 -0
- data/celerity.gemspec +126 -0
- data/lib/celerity/browser.rb +908 -0
- data/lib/celerity/clickable_element.rb +73 -0
- data/lib/celerity/collections.rb +164 -0
- data/lib/celerity/container.rb +800 -0
- data/lib/celerity/default_viewer.rb +14 -0
- data/lib/celerity/disabled_element.rb +40 -0
- data/lib/celerity/element.rb +311 -0
- data/lib/celerity/element_collection.rb +107 -0
- data/lib/celerity/element_locator.rb +164 -0
- data/lib/celerity/elements/button.rb +54 -0
- data/lib/celerity/elements/file_field.rb +29 -0
- data/lib/celerity/elements/form.rb +22 -0
- data/lib/celerity/elements/frame.rb +86 -0
- data/lib/celerity/elements/image.rb +89 -0
- data/lib/celerity/elements/label.rb +16 -0
- data/lib/celerity/elements/link.rb +43 -0
- data/lib/celerity/elements/meta.rb +14 -0
- data/lib/celerity/elements/non_control_elements.rb +124 -0
- data/lib/celerity/elements/option.rb +38 -0
- data/lib/celerity/elements/radio_check.rb +114 -0
- data/lib/celerity/elements/select_list.rb +146 -0
- data/lib/celerity/elements/table.rb +153 -0
- data/lib/celerity/elements/table_cell.rb +36 -0
- data/lib/celerity/elements/table_elements.rb +42 -0
- data/lib/celerity/elements/table_row.rb +49 -0
- data/lib/celerity/elements/text_field.rb +168 -0
- data/lib/celerity/exception.rb +83 -0
- data/lib/celerity/htmlunit/apache-mime4j-0.6.jar +0 -0
- data/lib/celerity/htmlunit/commons-codec-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-io-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-lang-2.5.jar +0 -0
- data/lib/celerity/htmlunit/commons-logging-1.1.1.jar +0 -0
- data/lib/celerity/htmlunit/cssparser-0.9.5.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-2.9-SNAPSHOT.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-core-js-2.8.jar +0 -0
- data/lib/celerity/htmlunit/httpclient-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/httpcore-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/httpmime-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/nekohtml-1.9.14.jar +0 -0
- data/lib/celerity/htmlunit/sac-1.3.jar +0 -0
- data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xercesImpl-2.9.1.jar +0 -0
- data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
- data/lib/celerity/htmlunit.rb +64 -0
- data/lib/celerity/identifier.rb +28 -0
- data/lib/celerity/ignoring_web_connection.rb +15 -0
- data/lib/celerity/input_element.rb +25 -0
- data/lib/celerity/javascript_debugger.rb +32 -0
- data/lib/celerity/listener.rb +143 -0
- data/lib/celerity/resources/no_viewer.png +0 -0
- data/lib/celerity/short_inspect.rb +20 -0
- data/lib/celerity/util.rb +129 -0
- data/lib/celerity/version.rb +3 -0
- data/lib/celerity/viewer_connection.rb +89 -0
- data/lib/celerity/watir_compatibility.rb +70 -0
- data/lib/celerity/xpath_support.rb +52 -0
- data/lib/celerity.rb +77 -0
- data/tasks/benchmark.rake +4 -0
- data/tasks/check.rake +24 -0
- data/tasks/clean.rake +3 -0
- data/tasks/fix.rake +25 -0
- data/tasks/jar.rake +55 -0
- data/tasks/jeweler.rake +28 -0
- data/tasks/rdoc.rake +4 -0
- data/tasks/snapshot.rake +25 -0
- data/tasks/spec.rake +26 -0
- data/tasks/website.rake +10 -0
- data/tasks/yard.rake +16 -0
- metadata +204 -0
@@ -0,0 +1,908 @@
|
|
1
|
+
module Celerity
|
2
|
+
class Browser
|
3
|
+
include Container
|
4
|
+
include XpathSupport
|
5
|
+
|
6
|
+
attr_accessor :page, :object, :charset, :headers
|
7
|
+
attr_reader :webclient, :viewer, :options
|
8
|
+
|
9
|
+
#
|
10
|
+
# Initialize a browser and go to the given URL
|
11
|
+
#
|
12
|
+
# @param [String] uri The URL to go to.
|
13
|
+
# @return [Celerity::Browser] instance.
|
14
|
+
#
|
15
|
+
|
16
|
+
def self.start(uri)
|
17
|
+
browser = new
|
18
|
+
browser.goto(uri)
|
19
|
+
browser
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Not implemented. Use ClickableElement#click_and_attach instead.
|
24
|
+
#
|
25
|
+
|
26
|
+
def self.attach(*args)
|
27
|
+
raise NotImplementedError, "use ClickableElement#click_and_attach instead"
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Creates a browser object.
|
32
|
+
#
|
33
|
+
# @see Celerity::Container for an introduction to the main API.
|
34
|
+
#
|
35
|
+
# @option opts :browser [:internet_explorer, :firefox, :firefox3] (:firefox3) Set the BrowserVersion used by HtmlUnit. Defaults to Firefox 3.
|
36
|
+
# @option opts :charset [String] ("UTF-8") Specify the charset that webclient will use for requests.
|
37
|
+
# @option opts :css [Boolean] (true) Enable/disable CSS. Enabled by default.
|
38
|
+
# @option opts :ignore_pattern [Regexp] See Browser#ignore_pattern=
|
39
|
+
# @option opts :javascript_enabled [Boolean] (true) Enable/disable JavaScript evaluation. Enabled by default.
|
40
|
+
# @option opts :javascript_exceptions [Boolean] (false) Raise exceptions on script errors. Disabled by default.
|
41
|
+
# @option opts :log_level [Symbol] (:warning) @see log_level=
|
42
|
+
# @option opts :proxy [String] (nil) Proxy server to use, in address:port format.
|
43
|
+
# @option opts :refresh_handler [:immediate, :waiting, :threaded] (:immediate) Set HtmlUnit's refresh handler.
|
44
|
+
# @option opts :render [:html, :xml] (:html) What DOM representation to send to connected viewers.
|
45
|
+
# @option opts :resynchronize [Boolean] (false) Use HtmlUnit::NicelyResynchronizingAjaxController to resynchronize Ajax calls.
|
46
|
+
# @option opts :secure_ssl [Boolean] (true) Enable/disable secure SSL. Enabled by default.
|
47
|
+
# @option opts :status_code_exceptions [Boolean] (false) Raise exceptions on failing status codes (404 etc.). Disabled by default.
|
48
|
+
# @option opts :user_agent [String] Override the User-Agent set by the :browser option
|
49
|
+
# @option opts :viewer [String, false] ("127.0.0.1:6429") Connect to a CelerityViewer if available.
|
50
|
+
#
|
51
|
+
# @return [Celerity::Browser] An instance of the browser.
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
#
|
55
|
+
|
56
|
+
def initialize(opts = {})
|
57
|
+
unless opts.is_a?(Hash)
|
58
|
+
raise TypeError, "wrong argument type #{opts.class}, expected Hash"
|
59
|
+
end
|
60
|
+
|
61
|
+
unless (render_types = [:html, :xml, nil, 'html', 'xml']).include?(opts[:render])
|
62
|
+
raise ArgumentError, "expected one of #{render_types.inspect} for key :render"
|
63
|
+
end
|
64
|
+
|
65
|
+
@options = opts.dup # keep the unmodified version around as well
|
66
|
+
opts = opts.dup # we'll delete from opts, so dup to avoid side effects
|
67
|
+
|
68
|
+
@render_type = opts.delete(:render) || :html
|
69
|
+
@charset = opts.delete(:charset) || "UTF-8"
|
70
|
+
@headers = {}
|
71
|
+
@page = nil
|
72
|
+
@error_checkers = []
|
73
|
+
@browser = self # for Container#browser
|
74
|
+
|
75
|
+
setup_webclient opts
|
76
|
+
setup_viewer opts.delete(:viewer)
|
77
|
+
|
78
|
+
self.log_level = opts.delete(:log_level) || :off
|
79
|
+
|
80
|
+
raise ArgumentError, "unknown option #{opts.inspect}" unless opts.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def inspect
|
84
|
+
short_inspect :exclude => %w[@webclient @browser @object @options @listener @event_listener]
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Goto the given URL
|
89
|
+
#
|
90
|
+
# @param [String] uri The url.
|
91
|
+
# @return [String] The url.
|
92
|
+
#
|
93
|
+
|
94
|
+
def goto(uri)
|
95
|
+
uri = "http://#{uri}" unless uri =~ %r{://}
|
96
|
+
|
97
|
+
puts "uri: #{uri}"
|
98
|
+
|
99
|
+
request = HtmlUnit::WebRequestSettings.new(::Java::JavaNet::URL.new(uri))
|
100
|
+
request.setCharset(@charset)
|
101
|
+
|
102
|
+
self.headers.each_pair do |name,value|
|
103
|
+
puts "#{name}: #{value}"
|
104
|
+
request.setAdditionalHeader(name,value)
|
105
|
+
end
|
106
|
+
|
107
|
+
rescue_status_code_exception do
|
108
|
+
self.page = @webclient.getPage(request)
|
109
|
+
end
|
110
|
+
|
111
|
+
url()
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Set the credentials used for basic HTTP authentication. (Celerity only)
|
116
|
+
#
|
117
|
+
# Example:
|
118
|
+
# browser.credentials = "username:password"
|
119
|
+
#
|
120
|
+
# @param [String] A string with username / password, separated by a colon
|
121
|
+
#
|
122
|
+
|
123
|
+
def credentials=(string)
|
124
|
+
user, pass = string.split(":")
|
125
|
+
dcp = HtmlUnit::DefaultCredentialsProvider.new
|
126
|
+
dcp.addCredentials(user, pass)
|
127
|
+
@webclient.setCredentialsProvider(dcp)
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Unsets the current page / closes all windows
|
132
|
+
#
|
133
|
+
|
134
|
+
def close
|
135
|
+
@page = nil
|
136
|
+
@webclient.closeAllWindows
|
137
|
+
@viewer.close
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# @return [String] the URL of the current page
|
142
|
+
#
|
143
|
+
|
144
|
+
def url
|
145
|
+
assert_exists
|
146
|
+
@page.getWebResponse.getWebRequest.getUrl.toString
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# @return [String] the title of the current page
|
151
|
+
#
|
152
|
+
|
153
|
+
def title
|
154
|
+
@page ? @page.getTitleText : ''
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# @return [String] the value of window.status
|
159
|
+
#
|
160
|
+
|
161
|
+
def status
|
162
|
+
execute_script "window.status" # avoid the listener overhead
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# @return [String] the HTML content of the current page
|
167
|
+
#
|
168
|
+
|
169
|
+
def html
|
170
|
+
return '' unless @page
|
171
|
+
|
172
|
+
@page.getWebResponse.getContentAsString(@charset)
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# @return [String] the XML representation of the DOM
|
177
|
+
#
|
178
|
+
|
179
|
+
def xml
|
180
|
+
return '' unless @page
|
181
|
+
return @page.asXml if @page.respond_to?(:asXml)
|
182
|
+
return text # fallback to text (for exampel for "plain/text" pages)
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# @return [String] a text representation of the current page
|
187
|
+
#
|
188
|
+
|
189
|
+
def text
|
190
|
+
return '' unless @page
|
191
|
+
|
192
|
+
if @page.respond_to?(:getContent)
|
193
|
+
@page.getContent.strip
|
194
|
+
elsif @page.respond_to?(:getDocumentElement) && doc = @page.getDocumentElement
|
195
|
+
doc.asText.strip
|
196
|
+
else
|
197
|
+
''
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
#
|
202
|
+
# @return [Hash] response headers as a hash
|
203
|
+
#
|
204
|
+
|
205
|
+
def response_headers
|
206
|
+
return {} unless @page
|
207
|
+
|
208
|
+
Hash[*@page.getWebResponse.getResponseHeaders.map { |obj| [obj.name, obj.value] }.flatten]
|
209
|
+
end
|
210
|
+
|
211
|
+
#
|
212
|
+
# @return [Fixnum] status code of the last request
|
213
|
+
#
|
214
|
+
|
215
|
+
def status_code
|
216
|
+
@page.getWebResponse.getStatusCode
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
# @return [String] content-type as in 'text/html'
|
221
|
+
#
|
222
|
+
|
223
|
+
def content_type
|
224
|
+
return '' unless @page
|
225
|
+
|
226
|
+
@page.getWebResponse.getContentType
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# @return [IO, nil] page contents as an IO, returns nil if no page is loaded.
|
231
|
+
#
|
232
|
+
|
233
|
+
def io
|
234
|
+
return nil unless @page
|
235
|
+
|
236
|
+
@page.getWebResponse.getContentAsStream.to_io
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Check if the current page contains the given text.
|
241
|
+
#
|
242
|
+
# @param [String, Regexp] expected_text The text to look for.
|
243
|
+
# @return [Numeric, nil] The index of the matched text, or nil if it isn't found.
|
244
|
+
# @raise [TypeError]
|
245
|
+
#
|
246
|
+
|
247
|
+
def contains_text(expected_text)
|
248
|
+
return nil unless exist?
|
249
|
+
super
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# @return [HtmlUnit::HtmlHtml] the underlying HtmlUnit document.
|
254
|
+
#
|
255
|
+
|
256
|
+
def document
|
257
|
+
@object
|
258
|
+
end
|
259
|
+
|
260
|
+
#
|
261
|
+
# Goto back one history item
|
262
|
+
# @return [String] The url of the resulting page.
|
263
|
+
#
|
264
|
+
|
265
|
+
def back
|
266
|
+
@webclient.getCurrentWindow.getHistory.back
|
267
|
+
refresh_page_from_window
|
268
|
+
|
269
|
+
url
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# Go forward one history item
|
274
|
+
# @return [String] The url of the resulting page.
|
275
|
+
#
|
276
|
+
|
277
|
+
def forward
|
278
|
+
@webclient.getCurrentWindow.getHistory.forward
|
279
|
+
refresh_page_from_window
|
280
|
+
|
281
|
+
url
|
282
|
+
end
|
283
|
+
|
284
|
+
#
|
285
|
+
# Wait for javascript jobs to finish
|
286
|
+
#
|
287
|
+
|
288
|
+
def wait
|
289
|
+
assert_exists
|
290
|
+
@webclient.waitForBackgroundJavaScript(10000);
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Refresh the current page
|
295
|
+
#
|
296
|
+
|
297
|
+
def refresh
|
298
|
+
assert_exists
|
299
|
+
@page.refresh
|
300
|
+
end
|
301
|
+
|
302
|
+
#
|
303
|
+
# Clears all cookies. (Celerity only)
|
304
|
+
#
|
305
|
+
|
306
|
+
def clear_cookies
|
307
|
+
@webclient.getCookieManager.clearCookies
|
308
|
+
end
|
309
|
+
|
310
|
+
#
|
311
|
+
# Clears the cache of "compiled JavaScript files and parsed CSS snippets"
|
312
|
+
#
|
313
|
+
|
314
|
+
def clear_cache
|
315
|
+
@webclient.cache.clear
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
# Get the cookies for this session. (Celerity only)
|
320
|
+
#
|
321
|
+
# @return [Hash<domain, Hash<name, value>>]
|
322
|
+
#
|
323
|
+
|
324
|
+
def cookies
|
325
|
+
result = Hash.new { |hash, key| hash[key] = {} }
|
326
|
+
|
327
|
+
cookies = @webclient.getCookieManager.getCookies
|
328
|
+
cookies.each do |cookie|
|
329
|
+
result[cookie.getDomain][cookie.getName] = cookie.getValue
|
330
|
+
end
|
331
|
+
|
332
|
+
result
|
333
|
+
end
|
334
|
+
|
335
|
+
#
|
336
|
+
# Add a cookie with the given parameters (Celerity only)
|
337
|
+
#
|
338
|
+
# @param [String] domain
|
339
|
+
# @param [String] name
|
340
|
+
# @param [String] value
|
341
|
+
#
|
342
|
+
# @option opts :path [String] ("/") A path
|
343
|
+
# @option opts :expires [Time] (1 day from now) An expiration date
|
344
|
+
# @option opts :secure [Boolean] (false)
|
345
|
+
#
|
346
|
+
|
347
|
+
def add_cookie(domain, name, value, opts = {})
|
348
|
+
path = opts.delete(:path) || "/"
|
349
|
+
max_age = opts.delete(:expires) || (Time.now + 60*60*24) # not sure if this is correct
|
350
|
+
secure = opts.delete(:secure) || false
|
351
|
+
|
352
|
+
raise(ArgumentError, "unknown option: #{opts.inspect}") unless opts.empty?
|
353
|
+
|
354
|
+
cookie = HtmlUnit::Util::Cookie.new(domain, name, value, path, max_age, secure)
|
355
|
+
@webclient.getCookieManager.addCookie cookie
|
356
|
+
|
357
|
+
cookie
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Remove the cookie with the given domain and name (Celerity only)
|
362
|
+
#
|
363
|
+
# @param [String] domain
|
364
|
+
# @param [String] name
|
365
|
+
#
|
366
|
+
# @raise [CookieNotFoundError] if the cookie doesn't exist
|
367
|
+
#
|
368
|
+
|
369
|
+
def remove_cookie(domain, name)
|
370
|
+
cm = @webclient.getCookieManager
|
371
|
+
cookie = cm.getCookies.find { |c| c.getDomain == domain && c.getName == name }
|
372
|
+
|
373
|
+
if cookie.nil?
|
374
|
+
raise CookieNotFoundError, "no cookie with domain #{domain.inspect} and name #{name.inspect}"
|
375
|
+
end
|
376
|
+
|
377
|
+
cm.removeCookie(cookie)
|
378
|
+
end
|
379
|
+
|
380
|
+
#
|
381
|
+
# Execute the given JavaScript on the current page.
|
382
|
+
# @return [Object] The resulting Object
|
383
|
+
#
|
384
|
+
|
385
|
+
def execute_script(source)
|
386
|
+
assert_exists
|
387
|
+
@page.executeJavaScript(source.to_s).getJavaScriptResult
|
388
|
+
end
|
389
|
+
|
390
|
+
# experimental - should be removed?
|
391
|
+
def send_keys(keys)
|
392
|
+
keys = keys.gsub(/\s*/, '').scan(/((?:\{[A-Z]+?\})|.)/u).flatten
|
393
|
+
keys.each do |key|
|
394
|
+
element = @page.getFocusedElement
|
395
|
+
case key
|
396
|
+
when "{TAB}"
|
397
|
+
@page.tabToNextElement
|
398
|
+
when /\w/
|
399
|
+
element.type(key)
|
400
|
+
else
|
401
|
+
raise NotImplementedError
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
#
|
407
|
+
# Wait until the given block evaluates to true (Celerity only)
|
408
|
+
#
|
409
|
+
# @param [Fixnum] timeout Number of seconds to wait before timing out (default: 30).
|
410
|
+
# @yieldparam [Celerity::Browser] browser The browser instance.
|
411
|
+
# @see Celerity::Browser#resynchronized
|
412
|
+
#
|
413
|
+
|
414
|
+
def wait_until(timeout = 30, &block)
|
415
|
+
returned = nil
|
416
|
+
|
417
|
+
Timeout.timeout(timeout) do
|
418
|
+
until returned = yield(self)
|
419
|
+
refresh_page_from_window
|
420
|
+
sleep 0.1
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
returned
|
425
|
+
end
|
426
|
+
|
427
|
+
#
|
428
|
+
# Wait while the given block evaluates to true (Celerity only)
|
429
|
+
#
|
430
|
+
# @param [Fixnum] timeout Number of seconds to wait before timing out (default: 30).
|
431
|
+
# @yieldparam [Celerity::Browser] browser The browser instance.
|
432
|
+
# @see Celerity::Browser#resynchronized
|
433
|
+
#
|
434
|
+
|
435
|
+
def wait_while(timeout = 30, &block)
|
436
|
+
returned = nil
|
437
|
+
|
438
|
+
Timeout.timeout(timeout) do
|
439
|
+
while returned = yield(self)
|
440
|
+
refresh_page_from_window
|
441
|
+
sleep 0.1
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
returned
|
446
|
+
end
|
447
|
+
|
448
|
+
#
|
449
|
+
# Allows you to temporarily switch to HtmlUnit's NicelyResynchronizingAjaxController
|
450
|
+
# to resynchronize ajax calls.
|
451
|
+
#
|
452
|
+
# @browser.resynchronized do |b|
|
453
|
+
# b.link(:id, 'trigger_ajax_call').click
|
454
|
+
# end
|
455
|
+
#
|
456
|
+
# @yieldparam [Celerity::Browser] browser The current browser object.
|
457
|
+
# @see Celerity::Browser#new for how to configure the browser to always use this.
|
458
|
+
#
|
459
|
+
|
460
|
+
def resynchronized(&block)
|
461
|
+
old_controller = @webclient.ajaxController
|
462
|
+
@webclient.setAjaxController(::HtmlUnit::NicelyResynchronizingAjaxController.new)
|
463
|
+
yield self
|
464
|
+
@webclient.setAjaxController(old_controller)
|
465
|
+
end
|
466
|
+
|
467
|
+
#
|
468
|
+
# Allows you to temporarliy switch to HtmlUnit's default AjaxController, so
|
469
|
+
# ajax calls are performed asynchronously. This is useful if you have created
|
470
|
+
# the Browser with :resynchronize => true, but want to switch it off temporarily.
|
471
|
+
#
|
472
|
+
# @yieldparam [Celerity::Browser] browser The current browser object.
|
473
|
+
# @see Celerity::Browser#new
|
474
|
+
#
|
475
|
+
|
476
|
+
def asynchronized(&block)
|
477
|
+
old_controller = @webclient.ajaxController
|
478
|
+
@webclient.setAjaxController(::HtmlUnit::AjaxController.new)
|
479
|
+
yield self
|
480
|
+
@webclient.setAjaxController(old_controller)
|
481
|
+
end
|
482
|
+
|
483
|
+
#
|
484
|
+
# Start or stop HtmlUnit's DebuggingWebConnection. (Celerity only)
|
485
|
+
# The output will go to /tmp/«name»
|
486
|
+
#
|
487
|
+
# @param [String] name directory name
|
488
|
+
# @param [block] blk block to execute
|
489
|
+
#
|
490
|
+
|
491
|
+
def debug_web_connection(name, &blk)
|
492
|
+
old_wc = @webclient.getWebConnection
|
493
|
+
|
494
|
+
@webclient.setWebConnection HtmlUnit::Util::DebuggingWebConnection.new(old_wc, name)
|
495
|
+
res = yield
|
496
|
+
@webclient.setWebConnection old_wc
|
497
|
+
|
498
|
+
res
|
499
|
+
end
|
500
|
+
|
501
|
+
def trace_javascript(debugger_klass = Celerity::JavascriptDebugger, &blk)
|
502
|
+
context_factory = @webclient.getJavaScriptEngine.getContextFactory
|
503
|
+
context_factory.setDebugger debugger_klass.new
|
504
|
+
yield
|
505
|
+
context_factory.setDebugger nil
|
506
|
+
end
|
507
|
+
|
508
|
+
#
|
509
|
+
# Add a listener block for one of the available types. (Celerity only)
|
510
|
+
# Types map to HtmlUnit interfaces like this:
|
511
|
+
#
|
512
|
+
# :status => StatusHandler
|
513
|
+
# :alert => AlertHandler ( window.alert() )
|
514
|
+
# :web_window_event => WebWindowListener
|
515
|
+
# :html_parser => HTMLParserListener
|
516
|
+
# :incorrectness => IncorrectnessListener
|
517
|
+
# :confirm => ConfirmHandler ( window.confirm() )
|
518
|
+
# :prompt => PromptHandler ( window.prompt() )
|
519
|
+
#
|
520
|
+
# Examples:
|
521
|
+
#
|
522
|
+
# browser.add_listener(:status) { |page, message| ... }
|
523
|
+
# browser.add_listener(:alert) { |page, message| ... }
|
524
|
+
# browser.add_listener(:web_window_event) { |web_window_event| ... }
|
525
|
+
# browser.add_listener(:html_parser) { |message, url, line, column, key| ... }
|
526
|
+
# browser.add_listener(:incorrectness) { |message, origin| ... }
|
527
|
+
# browser.add_listener(:confirm) { |page, message| ...; true }
|
528
|
+
# browser.add_listener(:prompt) { |page, message| ... }
|
529
|
+
#
|
530
|
+
#
|
531
|
+
# @param [Symbol] type One of the above symbols.
|
532
|
+
# @param [Proc] block A block to be executed for events of this type.
|
533
|
+
#
|
534
|
+
|
535
|
+
def add_listener(type, &block)
|
536
|
+
listener.add_listener(type, &block)
|
537
|
+
end
|
538
|
+
|
539
|
+
def remove_listener(type, block)
|
540
|
+
listener.remove_listener(type, block)
|
541
|
+
end
|
542
|
+
|
543
|
+
#
|
544
|
+
# Specify a boolean value to click either 'OK' or 'Cancel' in any confirm
|
545
|
+
# dialogs that might show up during the duration of the given block.
|
546
|
+
#
|
547
|
+
# (Celerity only)
|
548
|
+
#
|
549
|
+
# @param [Boolean] bool true to click 'OK', false to click 'cancel'
|
550
|
+
# @param [Proc] block A block that will trigger the confirm() call(s).
|
551
|
+
#
|
552
|
+
|
553
|
+
def confirm(bool, &block)
|
554
|
+
blk = lambda { bool }
|
555
|
+
|
556
|
+
listener.add_listener(:confirm, &blk)
|
557
|
+
yield
|
558
|
+
listener.remove_listener(:confirm, blk)
|
559
|
+
end
|
560
|
+
|
561
|
+
#
|
562
|
+
# Add a 'checker' proc that will be run on every page load
|
563
|
+
#
|
564
|
+
# @param [Proc] checker The proc to be run (can also be given as a block)
|
565
|
+
# @yieldparam [Celerity::Browser] browser The current browser object.
|
566
|
+
# @raise [ArgumentError] if no Proc or block was given.
|
567
|
+
#
|
568
|
+
|
569
|
+
def add_checker(checker = nil, &block)
|
570
|
+
if block_given?
|
571
|
+
@error_checkers << block
|
572
|
+
elsif Proc === checker
|
573
|
+
@error_checkers << checker
|
574
|
+
else
|
575
|
+
raise ArgumentError, "argument must be a Proc or block"
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
#
|
580
|
+
# Remove the given checker from the list of checkers
|
581
|
+
# @param [Proc] checker The Proc to disable.
|
582
|
+
#
|
583
|
+
|
584
|
+
def disable_checker(checker)
|
585
|
+
@error_checkers.delete(checker)
|
586
|
+
end
|
587
|
+
|
588
|
+
#
|
589
|
+
# :finest, :finer, :fine, :config, :info, :warning, :severe, or :off, :all
|
590
|
+
#
|
591
|
+
# @return [Symbol] the current log level
|
592
|
+
#
|
593
|
+
|
594
|
+
def log_level
|
595
|
+
Celerity::Util.logger_for('com.gargoylesoftware.htmlunit').level.to_s.downcase.to_sym
|
596
|
+
end
|
597
|
+
|
598
|
+
#
|
599
|
+
# Set Java log level (default is :warning, can be any of :all, :finest, :finer, :fine, :config, :info, :warning, :severe, :off)
|
600
|
+
#
|
601
|
+
# @param [Symbol] level The new log level.
|
602
|
+
#
|
603
|
+
|
604
|
+
def log_level=(level)
|
605
|
+
log_level = java.util.logging.Level.const_get(level.to_s.upcase)
|
606
|
+
|
607
|
+
[ 'com.gargoylesoftware.htmlunit',
|
608
|
+
'com.gargoylesoftware.htmlunit.html',
|
609
|
+
'com.gargoylesoftware.htmlunit.javascript',
|
610
|
+
'org.apache.commons.httpclient'
|
611
|
+
].each { |package| Celerity::Util.logger_for(package).level = log_level }
|
612
|
+
|
613
|
+
level
|
614
|
+
end
|
615
|
+
|
616
|
+
#
|
617
|
+
# If a request is made to an URL that matches the pattern set here, Celerity
|
618
|
+
# will ignore the request and return an empty page with content type "text/html" instead.
|
619
|
+
#
|
620
|
+
# This is useful to block unwanted requests (like ads/banners).
|
621
|
+
#
|
622
|
+
|
623
|
+
def ignore_pattern=(regexp)
|
624
|
+
unless regexp.kind_of?(Regexp)
|
625
|
+
raise TypeError, "expected Regexp, got #{regexp.inspect}:#{regexp.class}"
|
626
|
+
end
|
627
|
+
|
628
|
+
Celerity::IgnoringWebConnection.new(@webclient, regexp)
|
629
|
+
end
|
630
|
+
|
631
|
+
#
|
632
|
+
# Checks if we have a page currently loaded.
|
633
|
+
# @return [true, false]
|
634
|
+
#
|
635
|
+
|
636
|
+
def exist?
|
637
|
+
!!@page
|
638
|
+
end
|
639
|
+
alias_method :exists?, :exist?
|
640
|
+
|
641
|
+
#
|
642
|
+
# Turn on/off javascript exceptions
|
643
|
+
#
|
644
|
+
# @param [Bool]
|
645
|
+
#
|
646
|
+
|
647
|
+
def javascript_exceptions=(bool)
|
648
|
+
@webclient.throwExceptionOnScriptError = bool
|
649
|
+
end
|
650
|
+
|
651
|
+
def javascript_exceptions
|
652
|
+
@webclient.throwExceptionOnScriptError
|
653
|
+
end
|
654
|
+
|
655
|
+
#
|
656
|
+
# Turn on/off status code exceptions
|
657
|
+
#
|
658
|
+
# @param [Bool]
|
659
|
+
#
|
660
|
+
|
661
|
+
def status_code_exceptions=(bool)
|
662
|
+
@webclient.throwExceptionOnFailingStatusCode = bool
|
663
|
+
end
|
664
|
+
|
665
|
+
def status_code_exceptions
|
666
|
+
@webclient.throwExceptionOnFailingStatusCode
|
667
|
+
end
|
668
|
+
|
669
|
+
#
|
670
|
+
# Turn on/off CSS loading
|
671
|
+
#
|
672
|
+
# @param [Bool]
|
673
|
+
#
|
674
|
+
|
675
|
+
def css=(bool)
|
676
|
+
@webclient.cssEnabled = bool
|
677
|
+
end
|
678
|
+
|
679
|
+
def css
|
680
|
+
@webclient.cssEnabled
|
681
|
+
end
|
682
|
+
|
683
|
+
def refresh_handler=(symbol)
|
684
|
+
handler = case symbol
|
685
|
+
when :waiting
|
686
|
+
HtmlUnit::WaitingRefreshHandler.new
|
687
|
+
when :threaded
|
688
|
+
HtmlUnit::ThreadedRefreshHandler.new
|
689
|
+
when :immediate
|
690
|
+
HtmlUnit::ImmediateRefreshHandler.new
|
691
|
+
else
|
692
|
+
raise ArgumentError, "expected :waiting, :threaded or :immediate"
|
693
|
+
end
|
694
|
+
|
695
|
+
@webclient.setRefreshHandler handler
|
696
|
+
end
|
697
|
+
|
698
|
+
#
|
699
|
+
# Turn on/off secure SSL
|
700
|
+
#
|
701
|
+
# @param [Bool]
|
702
|
+
#
|
703
|
+
|
704
|
+
def secure_ssl=(bool)
|
705
|
+
@webclient.useInsecureSSL = !bool
|
706
|
+
end
|
707
|
+
|
708
|
+
#
|
709
|
+
# Turn on/off JavaScript execution
|
710
|
+
#
|
711
|
+
# @param [Bool]
|
712
|
+
#
|
713
|
+
|
714
|
+
def javascript_enabled=(bool)
|
715
|
+
@webclient.setJavaScriptEnabled(bool)
|
716
|
+
end
|
717
|
+
|
718
|
+
def javascript_enabled
|
719
|
+
@webclient.isJavaScriptEnabled
|
720
|
+
end
|
721
|
+
|
722
|
+
#
|
723
|
+
# Open the JavaScript debugger GUI
|
724
|
+
#
|
725
|
+
|
726
|
+
def visual_debugger
|
727
|
+
HtmlUnit::Util::WebClientUtils.attachVisualDebugger @webclient
|
728
|
+
end
|
729
|
+
|
730
|
+
#
|
731
|
+
# Sets the current page object for the browser
|
732
|
+
#
|
733
|
+
# @param [HtmlUnit::HtmlPage] value The page to set.
|
734
|
+
# @api private
|
735
|
+
#
|
736
|
+
|
737
|
+
def page=(value)
|
738
|
+
return if @page == value
|
739
|
+
@page = value
|
740
|
+
|
741
|
+
if @page.respond_to?("getDocumentElement")
|
742
|
+
@object = @page.getDocumentElement || @object
|
743
|
+
elsif @page.is_a? HtmlUnit::UnexpectedPage
|
744
|
+
raise UnexpectedPageException, @page.getWebResponse.getContentType
|
745
|
+
end
|
746
|
+
|
747
|
+
render unless @viewer == DefaultViewer
|
748
|
+
run_error_checks
|
749
|
+
|
750
|
+
value
|
751
|
+
end
|
752
|
+
|
753
|
+
#
|
754
|
+
# Check that we have a @page object.
|
755
|
+
#
|
756
|
+
# @raise [UnknownObjectException] if no page is loaded.
|
757
|
+
# @api private
|
758
|
+
#
|
759
|
+
|
760
|
+
def assert_exists
|
761
|
+
raise UnknownObjectException, "no page loaded" unless exist?
|
762
|
+
end
|
763
|
+
|
764
|
+
#
|
765
|
+
# Returns the element that currently has the focus (Celerity only)
|
766
|
+
#
|
767
|
+
|
768
|
+
def focused_element
|
769
|
+
element_from_dom_node(page.getFocusedElement())
|
770
|
+
end
|
771
|
+
|
772
|
+
#
|
773
|
+
# Enable Celerity's internal WebWindowEventListener
|
774
|
+
#
|
775
|
+
# @api private
|
776
|
+
#
|
777
|
+
|
778
|
+
def enable_event_listener
|
779
|
+
@event_listener ||= lambda do |event|
|
780
|
+
self.page = @page ? @page.getEnclosingWindow.getEnclosedPage : event.getNewPage
|
781
|
+
end
|
782
|
+
|
783
|
+
listener.add_listener(:web_window_event, &@event_listener)
|
784
|
+
end
|
785
|
+
|
786
|
+
#
|
787
|
+
# Disable Celerity's internal WebWindowEventListener
|
788
|
+
#
|
789
|
+
# @api private
|
790
|
+
#
|
791
|
+
|
792
|
+
def disable_event_listener
|
793
|
+
listener.remove_listener(:web_window_event, @event_listener)
|
794
|
+
|
795
|
+
if block_given?
|
796
|
+
result = yield
|
797
|
+
enable_event_listener
|
798
|
+
|
799
|
+
result
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
private
|
804
|
+
|
805
|
+
#
|
806
|
+
# Runs the all the checker procs added by +add_checker+
|
807
|
+
#
|
808
|
+
# @see add_checker
|
809
|
+
# @api private
|
810
|
+
#
|
811
|
+
|
812
|
+
def run_error_checks
|
813
|
+
@error_checkers.each { |e| e[self] }
|
814
|
+
end
|
815
|
+
|
816
|
+
#
|
817
|
+
# Configure the webclient according to the options given to #new.
|
818
|
+
# @see initialize
|
819
|
+
#
|
820
|
+
|
821
|
+
def setup_webclient(opts)
|
822
|
+
browser = (opts.delete(:browser) || :firefox3).to_sym
|
823
|
+
|
824
|
+
browser_version = case browser
|
825
|
+
when :firefox, :ff, :ff2
|
826
|
+
::HtmlUnit::BrowserVersion::FIREFOX_2
|
827
|
+
when :firefox3, :ff3
|
828
|
+
::HtmlUnit::BrowserVersion::FIREFOX_3
|
829
|
+
when :internet_explorer, :ie
|
830
|
+
::HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7
|
831
|
+
else
|
832
|
+
raise ArgumentError, "unknown browser: #{browser.inspect}"
|
833
|
+
end
|
834
|
+
|
835
|
+
if ua = opts.delete(:user_agent)
|
836
|
+
browser_version.setUserAgent(ua)
|
837
|
+
end
|
838
|
+
|
839
|
+
@webclient = if proxy = opts.delete(:proxy)
|
840
|
+
phost, pport = proxy.split(":")
|
841
|
+
::HtmlUnit::WebClient.new(browser_version, phost, pport.to_i)
|
842
|
+
else
|
843
|
+
::HtmlUnit::WebClient.new(browser_version)
|
844
|
+
end
|
845
|
+
|
846
|
+
self.javascript_exceptions = false unless opts.delete(:javascript_exceptions)
|
847
|
+
self.status_code_exceptions = false unless opts.delete(:status_code_exceptions)
|
848
|
+
self.css = opts.delete(:css) if opts[:css]
|
849
|
+
self.javascript_enabled = opts.delete(:javascript_enabled) != false
|
850
|
+
self.secure_ssl = opts.delete(:secure_ssl) != false
|
851
|
+
self.ignore_pattern = opts.delete(:ignore_pattern) if opts[:ignore_pattern]
|
852
|
+
self.refresh_handler = opts.delete(:refresh_handler) if opts[:refresh_handler]
|
853
|
+
|
854
|
+
if opts.delete(:resynchronize)
|
855
|
+
controller = ::HtmlUnit::NicelyResynchronizingAjaxController.new
|
856
|
+
@webclient.setAjaxController controller
|
857
|
+
end
|
858
|
+
|
859
|
+
enable_event_listener
|
860
|
+
end
|
861
|
+
|
862
|
+
def setup_viewer(option)
|
863
|
+
@viewer = DefaultViewer
|
864
|
+
return if option == false
|
865
|
+
|
866
|
+
host_string = option.kind_of?(String) ? option : "127.0.0.1:6429"
|
867
|
+
host, port = host_string.split(":")
|
868
|
+
|
869
|
+
if viewer = ViewerConnection.create(host, port.to_i)
|
870
|
+
@viewer = viewer
|
871
|
+
end
|
872
|
+
rescue Errno::ECONNREFUSED, SocketError => e
|
873
|
+
nil
|
874
|
+
end
|
875
|
+
|
876
|
+
#
|
877
|
+
# This *should* be unneccessary, but sometimes the page we get from the
|
878
|
+
# window is different (ie. a different object) from our current @page
|
879
|
+
# (Used by #wait_while and #wait_until)
|
880
|
+
#
|
881
|
+
|
882
|
+
def refresh_page_from_window
|
883
|
+
new_page = @page.getEnclosingWindow.getEnclosedPage
|
884
|
+
|
885
|
+
if new_page && (new_page != @page)
|
886
|
+
self.page = new_page
|
887
|
+
else
|
888
|
+
Log.debug "unneccessary refresh"
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
#
|
893
|
+
# Render the current page on the connected viewer.
|
894
|
+
# @api private
|
895
|
+
#
|
896
|
+
|
897
|
+
def render
|
898
|
+
@viewer.render_html(self.send(@render_type), url)
|
899
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
|
900
|
+
@viewer = DefaultViewer
|
901
|
+
end
|
902
|
+
|
903
|
+
def listener
|
904
|
+
@listener ||= Celerity::Listener.new(@webclient)
|
905
|
+
end
|
906
|
+
|
907
|
+
end # Browser
|
908
|
+
end # Celerity
|