gless 1.0.2 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog.txt ADDED
@@ -0,0 +1,3 @@
1
+ - 1.1.0: 29 Jan 2013: By default, "element"s now ignore non-visible
2
+ elements on the page; use { :invisible => true } to get the old
3
+ behaviour.
@@ -3,8 +3,8 @@
3
3
  module TestGithub
4
4
  class SearchPage < TestGithub::BasePage
5
5
 
6
- element :search_input , :text_field , :class => 'text' , :validator => true
7
- element :search_button , :button , :text => 'Search' , :validator => true
6
+ element :search_input , :text_field , :class => 'search-page-input' , :validator => true
7
+ element :search_button , :button , :text => 'Search' , :validator => true
8
8
 
9
9
  url %r{^:base_url/search}
10
10
 
data/lib/gless/config.rb CHANGED
@@ -44,7 +44,8 @@ module Gless
44
44
  end
45
45
 
46
46
  # Get an element from the configuration. Takes an arbitrary
47
- # number of arguments; each is taken to be a hash key.
47
+ # number of arguments; each is taken to be a hash key. With no
48
+ # arguments, returns the whole configuration.
48
49
  #
49
50
  # @example
50
51
  #
@@ -53,11 +54,13 @@ module Gless
53
54
  # @return [Object] what's left after following each key; could be
54
55
  # basically anything.
55
56
  def get( *args )
57
+ if args.empty?
58
+ return @config
59
+ end
60
+
56
61
  return get_sub_tree( @config, *args )
57
62
  end
58
63
 
59
- # Merge arbitrary data back in to the configuration; used to
60
- # overwrite values that were pulled out of files or whatever.
61
64
  def merge(hash)
62
65
  @config.merge!(hash)
63
66
  end
data/lib/gless/session.rb CHANGED
@@ -105,24 +105,21 @@ module Gless
105
105
 
106
106
  log.debug "Session: check if we've changed pages: #{@browser.title}, #{@browser.url}, #{@previous_url}, #{@current_page}, #{@acceptable_pages}"
107
107
 
108
- # Changed URL means we've changed pages. Our current page no
109
- # longer being in the acceptable pages list means we *should*
110
- # have changed pages. So we check both.
111
- if @browser.url == @previous_url && @acceptable_pages.member?( @current_page )
108
+ # Changed URL means we've changed pages, probably by surprise
109
+ # since desired page changes happen in Gless::WrapWatir#click
110
+ if @browser.url == @previous_url
112
111
  log.debug "Session: doesn't look like we've moved."
113
112
  else
114
- # See if we're on one of the acceptable pages; wait until we
115
- # are for "timeout" seconds.
113
+ # See if we're on one of the acceptable pages. We do no
114
+ # significant waiting because Gless::WrapWatir#click should
115
+ # have handeled that.
116
116
  good_page=false
117
117
  new_page=nil
118
- @timeout.times do
119
- if @acceptable_pages.nil?
120
- # If we haven't gone anywhere yet, anything is good
121
- good_page = true
122
- new_page = @pages[@current_page]
123
- break
124
- end
125
-
118
+ if @acceptable_pages.nil?
119
+ # If we haven't gone anywhere yet, anything is good
120
+ good_page = true
121
+ new_page = @pages[@current_page]
122
+ else
126
123
  @acceptable_pages.each do |page|
127
124
  log.debug "Session: Checking our current url, #{@browser.url}, for a match in #{page.name}: #{@pages[page].match_url(@browser.url)}"
128
125
  if @pages[page].match_url(@browser.url)
@@ -130,20 +127,19 @@ module Gless
130
127
  @current_page = page
131
128
  new_page = @pages[page]
132
129
  log.debug "Session: we seem to be on #{page.name} at #{@browser.url}"
133
- break
134
130
  end
135
131
  end
136
-
137
- if good_page
138
- break
139
- end
140
- sleep 1
141
132
  end
142
133
 
143
134
  good_page.should be_true, "Current URL is #{@browser.url}, which doesn't match any of the acceptable pages: #{@acceptable_pages}"
144
135
 
145
- log.debug "Session: checking for arrival at #{new_page.class.name}"
146
- new_page.arrived?.should be_true
136
+ # While this is very thorough, it slows things down quite a
137
+ # bit, and should mostly be covered by
138
+ # Gless::WrapWatir#click ; leaving here in case we decide we
139
+ # need it later.
140
+ #
141
+ # log.debug "Session: checking for arrival at #{new_page.class.name}"
142
+ # new_page.arrived?.should be_true
147
143
 
148
144
  url=@browser.url
149
145
  log.debug "Session: refreshed browser URL: #{url}"
@@ -154,6 +150,8 @@ module Gless
154
150
  @previous_url = url
155
151
  end
156
152
 
153
+ # End of page checking code.
154
+
157
155
  cpage = @pages[@current_page]
158
156
 
159
157
  if m.inspect =~ /(password|login)/i or args.inspect =~ /(password|login)/i
@@ -264,15 +262,18 @@ module Gless
264
262
  sleep opts[:interval]
265
263
  end
266
264
  rescue Exception => e
267
- self.log.debug "Session: long_wait: Had an exception #{e}"
265
+ self.log.warn "Session: long_wait: Had an exception #{e}"
268
266
  if self.get_config :global, :debug
269
267
  self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.inspect}"
270
268
  self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.message}"
271
269
  self.log.debug "Session: long_wait: Had an exception in debug mode: #{e.backtrace.join("\n")}"
272
270
 
273
- self.log.debug "Session: long_wait: Had an exception, and you're in debug mode, so giving you a debugger."
271
+ self.log.debug "Session: long_wait: Had an exception, and you're in debug mode, so giving you a debugger. Use 'continue' to proceed."
274
272
  debugger
275
273
  end
274
+
275
+ self.log.debug "Session: long_wait: Retrying after exception."
276
+ retry
276
277
  end
277
278
 
278
279
  return false
@@ -314,6 +315,97 @@ module Gless
314
315
  end
315
316
  end
316
317
 
318
+ # Does the heavy lifting of moving between pages when an element
319
+ # has a new page destination. Mostly used by Gless::WrapWatir
320
+ #
321
+ # Note that this attempts to click on the button (or do whatever
322
+ # else the passed block does) many times in an attempt to get to
323
+ # the right page. If multiple attempts are a problem, you
324
+ # should circumvent this method; {WrapWatir#click_once} exists
325
+ # for this purpose.
326
+ #
327
+ # @param [Class, Symbol, Array] newpage The page(s) that we
328
+ # could be moving to; same idea as {acceptable_pages=}
329
+ #
330
+ # @yield A required Proc/code block that contains the action to
331
+ # take to attempt to change pages (i.e. clicking on a button
332
+ # or whatever). May be run multiple times, as the whole point
333
+ # here is to keep trying until it works.
334
+ #
335
+ # @return (Boolean, String) Returns both whether it managed to
336
+ # get to the page in question and, if not, what sort of errors
337
+ # were seen.
338
+ def change_pages click_destination
339
+ self.acceptable_pages = click_destination
340
+
341
+ log.debug "Session: change_pages: checking to see if we have changed pages: #{@browser.title}, #{@current_page}, #{@acceptable_pages}"
342
+
343
+ good_page = false
344
+ error_message = ''
345
+ new_page = nil
346
+
347
+ # See if we're on one of the acceptable pages; wait until we
348
+ # are for "timeout" seconds.
349
+ @timeout.times do
350
+ self.log.debug "Session: change_pages: yielding to passed block."
351
+ yield
352
+ self.log.debug "Session: change_pages: done yielding to passed block."
353
+
354
+ if @acceptable_pages.member?( @current_page )
355
+ good_page = true
356
+ break
357
+ else
358
+ if @acceptable_pages.nil?
359
+ # If we haven't gone anywhere yet, anything is good
360
+ log.debug "Session: change_pages: no acceptable pages, so accepting the current page."
361
+ good_page = true
362
+ new_page = @pages[@current_page]
363
+ break
364
+ end
365
+
366
+ url=@browser.url
367
+ log.debug "Session: change_pages: refreshed browser URL: #{url}"
368
+
369
+ @acceptable_pages.each do |page|
370
+ log.debug "Session: change_pages: Checking our current url, #{url}, for a match in #{page.name}: #{@pages[page].match_url(url)}"
371
+ if @pages[page].match_url(url) and @pages[page].arrived? == true
372
+ good_page = true
373
+ @current_page = page
374
+ new_page = @pages[page]
375
+ log.debug "Session: change_pages: we seem to be on #{page.name} at #{url}"
376
+ end
377
+ end
378
+
379
+ if not new_page.match_url(url)
380
+ good_page = false
381
+ error_message = "Current URL is #{url}, which doesn't match that expected for any of the acceptable pages: #{@acceptable_pages}"
382
+ next
383
+ end
384
+
385
+ log.debug "Session: change_pages: checking for arrival at #{new_page.class.name}"
386
+ if not new_page.arrived?
387
+ good_page = false
388
+ error_message = "The current page, at #{url}, doesn't have all of the elements for any of the acceptable pages: #{@acceptable_pages}"
389
+ next
390
+ end
391
+
392
+ if good_page == true
393
+ break
394
+ else
395
+ sleep 1
396
+ end
397
+ end
398
+ end
399
+
400
+ if good_page
401
+ log.info "Session: change_pages: We have successfully moved to page #{new_page.class.name}"
402
+
403
+ @previous_url = url
404
+ end
405
+
406
+ return good_page, error_message
407
+ end
408
+
317
409
  end
318
410
 
319
411
  end
@@ -18,10 +18,24 @@ module Gless
18
18
  # This shouldn't ever need to be used by a user; it's done
19
19
  # automatically by the +element+ class method.
20
20
  class Gless::WrapWatir
21
+ require 'rspec'
21
22
  include RSpec::Matchers
22
23
 
23
24
  # Sets up the wrapping.
24
25
  #
26
+ # As a special case, note that the selectors can include a :proc
27
+ # element, in which case this is taken to be a Proc that takes
28
+ # the browser as an argument. This is used for cases where
29
+ # finding the element has to happen at runtime or is
30
+ # particularily complicated. In this case the rest of the
31
+ # selectors should include notes the element for debugging
32
+ # purposes. An example of such an element:
33
+ #
34
+ # Gless::WrapWatir.new(@browser, @session, :input, { :custom => "the first input under the div for tab #{tab} with the id 'task_name'", :proc => Proc.new { |browser| browser.div( :id => "tabs-#{tab}" ).input( :id => 'task_name' ) } }, false )
35
+ #
36
+ # The wrapper only considers *visible* matching elements, unless
37
+ # the selectors include ":invisible => true".
38
+ #
25
39
  # @param [Gless::Browser] browser
26
40
  # @param [Gless::Session] session
27
41
  # @param [Symbol] orig_type The type of the element; normally
@@ -42,34 +56,139 @@ module Gless
42
56
  @session = session
43
57
  @orig_type = orig_type
44
58
  @orig_selector_args = orig_selector_args
45
- @elem = @browser.send(@orig_type, @orig_selector_args)
46
59
  @num_retries = 3
47
60
  @wait_time = 30
48
61
  @click_destination = click_destination
49
62
  end
50
63
 
64
+ # Finds the element in question; deals with the fact that the
65
+ # selector could actually be a Proc.
66
+ #
67
+ # Has no parameters because it uses @orig_type and
68
+ # @orig_selector_args. If @orig_selector_args has a :proc
69
+ # element, runs that with the browser as an argument, otherwise
70
+ # just passes those variables to the Watir browser as normal.
71
+ def find_elem
72
+ tries=0
73
+ begin
74
+ # Do we want to return more than on element?
75
+ multiples = false
76
+
77
+ if @orig_selector_args.has_key? :proc
78
+ # If it's a Proc, it can handle its own visibility checking
79
+ return @orig_selector_args[:proc].call @browser
80
+ else
81
+ # We want all the relevant elements, so force that if it's
82
+ # not what was asked for
83
+ type = @orig_type.to_s
84
+ if type =~ %r{s$}
85
+ multiples=true
86
+ else
87
+ if Watir::Container.method_defined?(type + 's')
88
+ type = type + 's'
89
+ elsif Watir::Container.method_defined?(type + 'es')
90
+ type = type + 'es'
91
+ end
92
+ end
93
+ @session.log.debug "WrapWatir: find_elem: elements type: #{type}"
94
+ elems = @browser.send(type, @orig_selector_args)
95
+ end
96
+
97
+ @session.log.debug "WrapWatir: find_elem: elements identified by #{trimmed_selectors.inspect} initial version: #{elems.inspect}"
98
+
99
+ if elems.nil? or elems.length == 0
100
+ @session.log.debug "WrapWatir: find_elem: can't find any element identified by #{trimmed_selectors.inspect}"
101
+ # Generally, watir-webdriver code expects *something*
102
+ # back, and uses .present? to see if it's really there, so
103
+ # we get the singleton to satisfy that need.
104
+ return @browser.send(@orig_type, @orig_selector_args)
105
+ end
106
+
107
+ # We got something unexpected; just give it back
108
+ if ! elems.is_a?(Watir::ElementCollection)
109
+ @session.log.debug "WrapWatir: find_elem: elements aren't a collection; returning them"
110
+ return elems
111
+ end
112
+
113
+ if multiples
114
+ # We're OK returning the whole set
115
+ @session.log.debug "WrapWatir: find_elem: multiples were requested; returning #{elems.inspect}"
116
+ return elems
117
+ elsif elems.length == 1
118
+ # It's not a collection; just return it.
119
+ @session.log.debug "WrapWatir: find_elem: only one item found; returning #{elems[0].inspect}"
120
+ return elems[0]
121
+ else
122
+ unless @orig_selector_args.has_key? :invisible and @orig_selector_args[:invisible]
123
+ if trimmed_selectors.inspect !~ /password/i
124
+ @session.log.debug "WrapWatir: find_elem: elements identified by #{trimmed_selectors.inspect} before visibility selection: #{elems.inspect}"
125
+ end
126
+
127
+ # Find only visible elements
128
+ elem = elems.find { |x| x.present? and x.visible? }
129
+
130
+ if elem.nil?
131
+ # If there *are* no visible ones, take what we've got
132
+ elem = elems[0]
133
+ end
134
+
135
+ if trimmed_selectors.inspect !~ /password/i
136
+ @session.log.debug "WrapWatir: find_elem: element identified by #{trimmed_selectors.inspect} after visibility selection: #{elem.inspect}"
137
+ end
138
+
139
+ return elem
140
+ end
141
+ end
142
+ rescue Exception => e
143
+ @session.log.warn "WrapWatir: find_elem: Had an exception #{e}"
144
+ if @session.get_config :global, :debug
145
+ @session.log.debug "WrapWatir: find_elem: Had an exception in debug mode: #{e.inspect}"
146
+ @session.log.debug "WrapWatir: find_elem: Had an exception in debug mode: #{e.message}"
147
+ @session.log.debug "WrapWatir: find_elem: Had an exception in debug mode: #{e.backtrace.join("\n")}"
148
+
149
+ @session.log.debug "WrapWatir: find_elem: Had an exception, and you're in debug mode, so giving you a debugger. Use 'continue' to proceed."
150
+ debugger
151
+ end
152
+
153
+ if tries < 3
154
+ @session.log.debug "WrapWatir: find_elem: Retrying after exception."
155
+ retry
156
+ else
157
+ @session.log.debug "WrapWatir: find_elem: Giving up after exception."
158
+ end
159
+ tries += 1
160
+ end
161
+ end
162
+
163
+ # Pulls any procs out of the selectors for debugging purposes
164
+ def trimmed_selectors
165
+ @orig_selector_args.reject { |k,v| k == :proc }
166
+ end
167
+
51
168
  # Passes everything through to the underlying Watir object, but
52
169
  # with logging.
53
170
  def method_missing(m, *args, &block)
54
171
  wrapper_logging(m, args)
55
- @elem.send(m, *args, &block)
172
+ find_elem.send(m, *args, &block)
56
173
  end
57
174
 
58
175
  # Used to log all pass through behaviours. In debug mode,
59
176
  # displays details about what method was passed through, and the
60
177
  # nature of the element in question.
61
178
  def wrapper_logging(m, args)
62
- if @orig_selector_args.inspect =~ /password/i
179
+ elem = find_elem
180
+
181
+ if trimmed_selectors.inspect =~ /password/i
63
182
  @session.log.debug "WrapWatir: Doing something with passwords, redacted."
64
183
  else
65
184
  if @session.get_config :global, :debug
66
185
  @session.log.add_to_replay_log( @browser, @session )
67
186
  end
68
187
 
69
- @session.log.debug "WrapWatir: Calling #{m} with arguments #{args.inspect} on a #{@elem.class.name} element identified by: #{@orig_selector_args.inspect}"
188
+ @session.log.debug "WrapWatir: Calling #{m} with arguments #{args.inspect} on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
70
189
 
71
- if @elem.present? && @elem.class.name == 'Watir::HTMLElement'
72
- @session.log.warn "FIXME: You have been lazy and said that something is of type 'element'; its actual type is #{@elem.to_subtype.class.name}; the element is identified by #{@orig_selector_args.inspect}"
190
+ if elem.present? && elem.class.name == 'Watir::HTMLElement'
191
+ @session.log.warn "FIXME: You have been lazy and said that something is of type 'element'; its actual type is #{elem.to_subtype.class.name}; the element is identified by #{trimmed_selectors.inspect}"
73
192
  end
74
193
  end
75
194
  end
@@ -77,14 +196,55 @@ module Gless
77
196
  # A wrapper around Watir's click; handles the changing of
78
197
  # acceptable pages (i.e. click_destination processing, see
79
198
  # {Gless::BasePage} and {Gless::Session} for more details).
80
- def click
199
+ #
200
+ # Unconditionally clicks once, without any error handling; if
201
+ # you want to try to execute a page transition no matter what,
202
+ # just use +click+
203
+ def click_once
204
+ elem = find_elem
205
+
81
206
  if @click_destination
82
- @session.log.debug "WrapWatir: A #{@elem.class.name} element identified by: #{@orig_selector_args.inspect} has a special destination when clicked, #{@click_destination}"
207
+ @session.log.debug "WrapWatir: A #{elem.class.name} element identified by: #{trimmed_selectors.inspect} has a special destination when clicked, #{@click_destination}"
83
208
  @session.acceptable_pages = @click_destination
84
209
  end
85
210
  wrapper_logging('click', nil)
86
- @session.log.debug "WrapWatir: Calling click on a #{@elem.class.name} element identified by: #{@orig_selector_args.inspect}"
87
- @elem.click
211
+ @session.log.debug "WrapWatir: Calling click on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
212
+ elem.click
213
+ end
214
+
215
+ # A wrapper around Watir's click; handles the changing of
216
+ # acceptable pages (i.e. click_destination processing, see
217
+ # {Gless::BasePage} and {Gless::Session} for more details).
218
+ #
219
+ # If you've clicked on an element with a click_destination, it
220
+ # then calls {Gless::Session#change_pages} to do the actual page
221
+ # transition. As such, it may actually click several times,
222
+ # it will keep trying until it works; if that's not what you're
223
+ # looking for, use click_once
224
+ def click
225
+ elem = find_elem
226
+
227
+ if @click_destination
228
+ @session.log.debug "WrapWatir: click: A #{elem.class.name} element identified by: #{trimmed_selectors.inspect} has a special destination when clicked, #{@click_destination}"
229
+ change_pages_out, change_pages_message = @session.change_pages( @click_destination ) do
230
+ wrapper_logging('click', nil)
231
+ @session.log.debug "WrapWatir: click: Calling click on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
232
+ if elem.exists?
233
+ elem.click
234
+ end
235
+ if block_given?
236
+ yield
237
+ end
238
+ end
239
+ # If the return value isn't true, use it as the message to
240
+ # print.
241
+ @session.log.debug "WrapWatir: click: change pages results: #{change_pages_out}, #{change_pages_message}"
242
+ change_pages_out.should be_true, change_pages_message
243
+ else
244
+ wrapper_logging('click', nil)
245
+ @session.log.debug "WrapWatir: click: Calling click on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
246
+ elem.click
247
+ end
88
248
  end
89
249
 
90
250
  # Used by +set+, see description there.
@@ -109,55 +269,55 @@ module Gless
109
269
  # such try.
110
270
  def set(*args)
111
271
  wrapper_logging('set', args)
272
+ elem = find_elem
112
273
 
113
274
  # Double-check text fields
114
- if @elem.class.name == 'Watir::TextField'
275
+ if elem.class.name == 'Watir::TextField'
115
276
  set_text = args[0]
116
- @elem.set(set_text)
277
+ @session.log.debug "WrapWatir: set: setting text on #{elem.inspect}/#{elem.html} to #{set_text}"
278
+ elem.set(set_text)
117
279
 
118
280
  @num_retries.times do |x|
119
281
  @session.log.debug "WrapWatir: Checking that text entry worked"
120
- @elem = @browser.send(@orig_type, @orig_selector_args)
121
- if @elem.value == set_text
282
+ if elem.value == set_text
122
283
  break
123
284
  else
124
285
  @session.log.debug "WrapWatir: It did not; sleeping for #{@wait_time} seconds"
125
286
  sleep @wait_time
126
287
  @session.log.debug "WrapWatir: Retrying."
127
288
  wrapper_logging('set', set_text)
128
- @elem.set(set_text)
289
+ elem.set(set_text)
129
290
  end
130
291
  end
131
- @elem.value.should == set_text
292
+ elem.value.to_s.should == set_text.to_s
132
293
  @session.log.debug "WrapWatir: The text entry worked"
133
294
 
134
295
  return self
135
296
 
136
297
  # Double-check radio buttons
137
- elsif @elem.class.name == 'Watir::Radio'
298
+ elsif elem.class.name == 'Watir::Radio'
138
299
  wrapper_logging('set', [])
139
- @elem.set
300
+ elem.set
140
301
 
141
302
  @num_retries.times do |x|
142
303
  @session.log.debug "WrapWatir: Checking that the radio selection worked"
143
- @elem = @browser.send(@orig_type, @orig_selector_args)
144
- if @elem.set? == true
304
+ if elem.set? == true
145
305
  break
146
306
  else
147
307
  @session.log.debug "WrapWatir: It did not; sleeping for #{@wait_time} seconds"
148
308
  sleep @wait_time
149
309
  @session.log.debug "WrapWatir: Retrying."
150
310
  wrapper_logging('set', [])
151
- @elem.set
311
+ elem.set
152
312
  end
153
313
  end
154
- @elem.set?.should be_true
314
+ elem.set?.should be_true
155
315
  @session.log.debug "WrapWatir: The radio set worked"
156
316
 
157
317
  return self
158
318
 
159
319
  else
160
- @elem.set(*args)
320
+ elem.set(*args)
161
321
  end
162
322
  end
163
323
  end
data/lib/gless.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  # project.
7
7
  module Gless
8
8
  # The current version number.
9
- VERSION = '1.0.2'
9
+ VERSION = '1.1.2'
10
10
 
11
11
  # Sets up the config, logger and browser instances, the ordering
12
12
  # of which is slightly tricky.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gless
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-08 00:00:00.000000000 Z
12
+ date: 2013-02-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -119,6 +119,7 @@ extra_rdoc_files: []
119
119
  files:
120
120
  - .gitignore
121
121
  - .rvmrc
122
+ - Changelog.txt
122
123
  - README.md
123
124
  - Rakefile
124
125
  - TODO