gless 1.2.0 → 1.3.1
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/Changelog.txt +4 -0
- data/examples/test_github/lib/pages/test_github/search_page.rb +13 -8
- data/lib/gless/base_page.rb +80 -12
- data/lib/gless/config.rb +40 -6
- data/lib/gless/session.rb +17 -5
- data/lib/gless/wrap_watir.rb +196 -31
- data/lib/gless.rb +8 -2
- metadata +2 -2
data/Changelog.txt
CHANGED
|
@@ -3,3 +3,7 @@
|
|
|
3
3
|
behaviour.
|
|
4
4
|
- 1.2.0: 10 Jun 2013: Changes by bairyn. Added caching, finding
|
|
5
5
|
based on parent element, and block validators.
|
|
6
|
+
- 1.3.0: 8 Aug 2013: Detect and work around errors caused by incorrect caching,
|
|
7
|
+
and add support for element arguments, evaluating elements under new parents,
|
|
8
|
+
finding based on children, and the :unique option to ensure only one element
|
|
9
|
+
matches a specification.
|
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
module TestGithub
|
|
4
4
|
class SearchPage < TestGithub::BasePage
|
|
5
5
|
|
|
6
|
-
element :search_form , :form , :id => 'search_form' , :validator => true
|
|
7
|
-
element :search_input , :text_field , :
|
|
6
|
+
element :search_form , :form , :id => 'search_form' , :validator => true , :child => :search_input
|
|
7
|
+
element :search_input , :text_field , :name => 'q' , :validator => true , :parent => :search_form
|
|
8
8
|
element :search_button , :button , :text => 'Search' , :validator => true , :parent => :search_form
|
|
9
9
|
|
|
10
|
+
element :repository_list , :ul , :class => /\brepolist\b/
|
|
11
|
+
element :repository_link , :link , :click_destination => :RepoPage , :parent => :repository_list , :proc => -> repos, page, name do
|
|
12
|
+
repos.a(:text => name)
|
|
13
|
+
end
|
|
14
|
+
element :repository_elems , :lis , :class => 'public source' , :parent => :repository_list
|
|
15
|
+
|
|
10
16
|
# Test validator blocks.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
add_validator do |browser, session|
|
|
18
|
+
browser.url =~ /search/
|
|
19
|
+
end
|
|
14
20
|
|
|
15
21
|
url %r{^:base_url/search}
|
|
16
22
|
set_entry_url ':base_url/search'
|
|
@@ -24,8 +30,7 @@ module TestGithub
|
|
|
24
30
|
|
|
25
31
|
def goto_repository name
|
|
26
32
|
@session.log.debug "SearchPage: goto_repository: name: #{name}"
|
|
27
|
-
(
|
|
28
|
-
@session.acceptable_pages = TestGithub::RepoPage
|
|
33
|
+
repository_link(name).click
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def find_repository name
|
|
@@ -42,7 +47,7 @@ module TestGithub
|
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def repositories
|
|
45
|
-
repos =
|
|
50
|
+
repos = repository_elems
|
|
46
51
|
|
|
47
52
|
@session.log.debug "SearchPage: repositories: repos: #{repos.inspect}"
|
|
48
53
|
|
data/lib/gless/base_page.rb
CHANGED
|
@@ -119,13 +119,25 @@ module Gless
|
|
|
119
119
|
# That's about as complicated as it gets.
|
|
120
120
|
#
|
|
121
121
|
# The first two arguments (name and type) are required. The
|
|
122
|
-
# rest is a hash.
|
|
123
|
-
# +:
|
|
122
|
+
# rest is a hash. Six options (see below) have special meaning:
|
|
123
|
+
# +:validator+, +:click_destination+, +:parent+, +:child+
|
|
124
|
+
# +:proc+, +:cache+, and +:unique+ (see below) have special meaning.
|
|
124
125
|
#
|
|
125
126
|
# Anything else is taken to be a Watir selector. If no
|
|
126
127
|
# selector is forthcoming, the name is taken to be the element
|
|
127
128
|
# id.
|
|
128
129
|
#
|
|
130
|
+
# The element can also be a collection of elements with the appropriate
|
|
131
|
+
# element type (e.g. +lis+, plural of +li+); however, if it is restricted
|
|
132
|
+
# by non-watir selectors (e.g. with :child), the collection is returned
|
|
133
|
+
# as an +Array+, since watir-webdriver does not support arbitrarily
|
|
134
|
+
# filtering elements from an +ElementCollection+. For
|
|
135
|
+
# reliability, the user can either ensure that the element is only used
|
|
136
|
+
# after being coerced into an array with +.to_a+ to ensure that the
|
|
137
|
+
# collection ends up as an Array in each case (unless the method used
|
|
138
|
+
# is supported by both element collections and arrays), or use a
|
|
139
|
+
# low-level +:proc+ to bypass gless's element finding procedure.
|
|
140
|
+
#
|
|
129
141
|
# @param [Symbol] basename The name used in the Gless user's code
|
|
130
142
|
# to refer to this element. This page object ends up with a
|
|
131
143
|
# method of this name.
|
|
@@ -149,18 +161,66 @@ module Gless
|
|
|
149
161
|
#
|
|
150
162
|
# @option opts [Symbol] :parent (nil) A symbol of a parent element
|
|
151
163
|
# to which matching is restricted.
|
|
152
|
-
#
|
|
164
|
+
#
|
|
165
|
+
# @option opts [Symbol, Array<Symbol>, Array<Array<Symbol>>] :child (nil)
|
|
166
|
+
# If present, this restricts element selection to elements that
|
|
167
|
+
# contain the child element. The parent of the child element is
|
|
168
|
+
# overridden with the element being tested; it is therefore safe to
|
|
169
|
+
# set the child element's parent to this one, since it won't result
|
|
170
|
+
# in circular reference. This is useful if an element on a page,
|
|
171
|
+
# that must contain a child element that can be located with its
|
|
172
|
+
# selectors, is used in another way. This can be set to an array to
|
|
173
|
+
# specify multiple children elements. Arguments can be specified in
|
|
174
|
+
# an Array. A :child can point to the Symbol of the child element. To
|
|
175
|
+
# specify multiple children, set :child to an Array of Symbols. To
|
|
176
|
+
# specify arguments to pass to each child, set :child to an Array of
|
|
177
|
+
# Arrays each containing the symbol of the child element and then the
|
|
178
|
+
# arguments passed to it. Examples of each usage:
|
|
179
|
+
#
|
|
180
|
+
# element :games_pane , :div , :class => 'pane' , :child => :tbs_list
|
|
181
|
+
# element :tbs_list , :ul , :class => 'list' , :child => [:tbs_header, :tbs_popular_list]
|
|
182
|
+
# element :tbs_pop_list , :ul , :class => 'list' , :child => [[:tbs_link, 'Battle Game 2'], [:tbs_link, 'Wars']]
|
|
183
|
+
#
|
|
184
|
+
# element :tbs_header , :h3 , :text => 'Turn Based Strategy Games'
|
|
185
|
+
# element :tbs_link , :link , :proc => -> parent, page, name {...}
|
|
186
|
+
#
|
|
153
187
|
# @option opts [Symbol] :cache (nil) If non-nil, overrides the default
|
|
154
188
|
# cache setting and determines whether caching is enabled for this
|
|
155
189
|
# element. If false, a new look-up will be performed each time the
|
|
156
190
|
# element is accessed, and, if true, a look-up will only be performed
|
|
157
191
|
# once until the session changes the page.
|
|
192
|
+
#
|
|
193
|
+
# @option opts [Symbol] :unique (false) If true, fail if multiple
|
|
194
|
+
# elements match the element's specification when the element is
|
|
195
|
+
# accessed. Note that this option has no effect on elements with
|
|
196
|
+
# +:proc+s.
|
|
158
197
|
#
|
|
159
198
|
# @option opts [Symbol] :proc (nil) If present, specifies a manual,
|
|
160
199
|
# low-level procedure to return a watir element, which overrides other
|
|
161
200
|
# selectors. When the watir element is needed, this procedure is
|
|
162
|
-
# called with the parent watir element passed as the argument (see
|
|
163
|
-
# +:parent+) if it exists, and otherwise the browser
|
|
201
|
+
# called with the parent watir element passed as the first argument (see
|
|
202
|
+
# +:parent+) if it exists, and otherwise the browser, along with the
|
|
203
|
+
# page as the second argument. Any arguments given to the element
|
|
204
|
+
# at runtime are passed to the procedure after the first, parent,
|
|
205
|
+
# argument. For example, given the definition
|
|
206
|
+
#
|
|
207
|
+
# element :book_list, :ul, :click_destination => :HomePage, :parent => :nonfiction, :proc => -> parent, page, author {...}
|
|
208
|
+
#
|
|
209
|
+
# then whenever +session.book_list "Robyn Dawes"+ is invoked, the procedure will be passed the
|
|
210
|
+
# +:nonfiction+ element, the page for which +:book_list+ was defined,
|
|
211
|
+
# and the string "Robyn Dawes", and should return a Watir element. In
|
|
212
|
+
# the block itself, +parent+ could be used as the root element (the
|
|
213
|
+
# browser with no root element), which can be different if the user
|
|
214
|
+
# decides to restrict the +:book_list+ element under a new parent (e.g.
|
|
215
|
+
# in invoking +@session.bilingual_pane.book_list, in which case parent
|
|
216
|
+
# would be set to the :bilingual_pane element). +page+ refers to the
|
|
217
|
+
# page object in which +:book_list+ is defined, which can be used to
|
|
218
|
+
# refer to other elements and methods on the same page. Any arguments
|
|
219
|
+
# passed to the element are given to the block.
|
|
220
|
+
#
|
|
221
|
+
# Different elements are cached for different
|
|
222
|
+
# arguments. Caching can be disabled for an individual
|
|
223
|
+
# element by passing :cache => false.
|
|
164
224
|
#
|
|
165
225
|
# @option opts [Object] ANY All other opts keys are used as
|
|
166
226
|
# Watir selectors to find the element on the page.
|
|
@@ -170,7 +230,7 @@ module Gless
|
|
|
170
230
|
|
|
171
231
|
# Promote various other things into selectors; do this before
|
|
172
232
|
# we add in the default below
|
|
173
|
-
non_selector_opts = [ :validator, :click_destination, :parent, :cache ]
|
|
233
|
+
non_selector_opts = [ :validator, :click_destination, :parent, :cache, :unique, :child ]
|
|
174
234
|
if ! opts[:selector]
|
|
175
235
|
opts[:selector] = {} if ! opts.keys.empty?
|
|
176
236
|
opts.keys.each do |key|
|
|
@@ -190,7 +250,19 @@ module Gless
|
|
|
190
250
|
click_destination = opts[:click_destination]
|
|
191
251
|
validator = opts[:validator]
|
|
192
252
|
parent = opts[:parent]
|
|
253
|
+
child = opts[:child]
|
|
254
|
+
if child.nil?
|
|
255
|
+
# No child
|
|
256
|
+
child = []
|
|
257
|
+
elsif child.kind_of? Symbol
|
|
258
|
+
# Single child
|
|
259
|
+
child = [[child]]
|
|
260
|
+
elsif (child.kind_of? Array) && (!child.empty?) && (child[0].kind_of? Symbol)
|
|
261
|
+
# Multiple children w/out arguments
|
|
262
|
+
child.map! {|s| [s]}
|
|
263
|
+
end
|
|
193
264
|
cache = opts[:cache]
|
|
265
|
+
unique = opts[:unique]
|
|
194
266
|
|
|
195
267
|
methname = basename.to_s.tr('-', '_').to_sym
|
|
196
268
|
|
|
@@ -206,8 +278,8 @@ module Gless
|
|
|
206
278
|
# $master_logger.debug "In GenericBasePage, for #{self.name}, element: #{basename} has a special destination when clicked, #{click_destination}"
|
|
207
279
|
end
|
|
208
280
|
|
|
209
|
-
define_method methname do
|
|
210
|
-
cached_elements[methname] ||= Gless::WrapWatir.new(@browser, @session, self, type, selector, click_destination, parent, cache)
|
|
281
|
+
define_method methname do |*args|
|
|
282
|
+
cached_elements[[methname, *args]] ||= Gless::WrapWatir.new(methname, @browser, @session, self, type, selector, click_destination, parent, child, cache, unique, *args)
|
|
211
283
|
end
|
|
212
284
|
end
|
|
213
285
|
|
|
@@ -398,10 +470,6 @@ module Gless
|
|
|
398
470
|
end
|
|
399
471
|
end
|
|
400
472
|
|
|
401
|
-
#******************************
|
|
402
|
-
# Object Level
|
|
403
|
-
#******************************
|
|
404
|
-
|
|
405
473
|
# @return [Hash] A hash of cached +WrapWatir+ elements indexed by the
|
|
406
474
|
# symbol name. This hash is cleared whenever the page changes.
|
|
407
475
|
attr_writer :cached_elements
|
data/lib/gless/config.rb
CHANGED
|
@@ -27,10 +27,10 @@ module Gless
|
|
|
27
27
|
#
|
|
28
28
|
# @return [Gless::EnvConfig]
|
|
29
29
|
def initialize
|
|
30
|
-
env = (ENV['ENVIRONMENT'] || 'development').to_sym
|
|
30
|
+
@env = (ENV['ENVIRONMENT'] || 'development').to_sym
|
|
31
31
|
|
|
32
|
-
env_file = "#{@@env_dir}/config/#{env}.yml"
|
|
33
|
-
raise "You need to create a configuration file named '#{env}.yml' (generated from the ENVIRONMENT environment variable) under #{@@env_dir}/lib/config" unless File.exists? env_file
|
|
32
|
+
env_file = "#{@@env_dir}/config/#{@env}.yml"
|
|
33
|
+
raise "You need to create a configuration file named '#{@env}.yml' (generated from the ENVIRONMENT environment variable) under #{@@env_dir}/lib/config" unless File.exists? env_file
|
|
34
34
|
|
|
35
35
|
@config = YAML::load_file env_file
|
|
36
36
|
end
|
|
@@ -54,28 +54,62 @@ module Gless
|
|
|
54
54
|
# @return [Object] what's left after following each key; could be
|
|
55
55
|
# basically anything.
|
|
56
56
|
def get( *args )
|
|
57
|
+
r = get_default nil, *args
|
|
58
|
+
raise "Could not locate '#{args.join '.'}' in YAML config; please ensure that '#{@env}.yml' is up to date." if r.nil?
|
|
59
|
+
r
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Optionally get an element from the configuration, otherwise returning the
|
|
63
|
+
# default value.
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
#
|
|
67
|
+
# @config.get_default false, :global, :cache
|
|
68
|
+
#
|
|
69
|
+
# @return [Object] what's left after following each key, or else the
|
|
70
|
+
# default value.
|
|
71
|
+
def get_default( default, *args )
|
|
57
72
|
if args.empty?
|
|
58
73
|
return @config
|
|
59
74
|
end
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
r = get_sub_tree( @config, *args )
|
|
77
|
+
r.nil? ? default : r
|
|
62
78
|
end
|
|
63
79
|
|
|
64
80
|
def merge(hash)
|
|
65
81
|
@config.merge!(hash)
|
|
66
82
|
end
|
|
67
83
|
|
|
84
|
+
# Set an element in the configuration to the given value, passed after all
|
|
85
|
+
# of the indices.
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
#
|
|
89
|
+
# @config.set :global, :debug, true
|
|
90
|
+
def set(*indices, value)
|
|
91
|
+
set_root @config, value, *indices
|
|
92
|
+
end
|
|
93
|
+
|
|
68
94
|
private
|
|
69
95
|
|
|
96
|
+
def set_root root, value, *indices
|
|
97
|
+
if indices.length > 1
|
|
98
|
+
set_root root[indices[0]], value, *indices[1..-1]
|
|
99
|
+
else
|
|
100
|
+
root[indices[0]] = value
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
70
104
|
# Recursively does all the heavy lifting for get
|
|
71
105
|
def get_sub_tree items, elem, *args
|
|
72
106
|
# Can't use debug logging here, as it maybe isn't turned on yet
|
|
73
107
|
# puts "In Gless::EnvConfig, get_sub_tree: items: #{items}, elem: #{elem}, args: #{args}"
|
|
74
108
|
|
|
75
|
-
|
|
109
|
+
return nil if items.nil?
|
|
76
110
|
|
|
77
111
|
new_items = items[elem.to_sym]
|
|
78
|
-
|
|
112
|
+
return nil if new_items.nil?
|
|
79
113
|
|
|
80
114
|
if args.empty?
|
|
81
115
|
return new_items
|
data/lib/gless/session.rb
CHANGED
|
@@ -83,6 +83,12 @@ module Gless
|
|
|
83
83
|
@config.get(*args)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
# Just passes through to the Gless::EnvConfig component's +get_default+
|
|
87
|
+
# method.
|
|
88
|
+
def get_config_default(*args)
|
|
89
|
+
@config.get_default(*args)
|
|
90
|
+
end
|
|
91
|
+
|
|
86
92
|
# Just a shortcut to get to the Gless::Logger object.
|
|
87
93
|
def log
|
|
88
94
|
@logger
|
|
@@ -174,12 +180,18 @@ module Gless
|
|
|
174
180
|
#
|
|
175
181
|
# @param [Class] pklas The class for the page object that has a
|
|
176
182
|
# set_entry_url that we are using.
|
|
177
|
-
|
|
183
|
+
# @param [Boolean] always (true) Whether to enter the given page even
|
|
184
|
+
def enter(pklas, always = true)
|
|
178
185
|
log.info "Session: Entering the site directly using the entry point for the #{pklas.name} page class"
|
|
179
|
-
|
|
180
|
-
@
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
|
|
187
|
+
if always || pklas != @current_page
|
|
188
|
+
@current_page = pklas
|
|
189
|
+
@pages[pklas].enter
|
|
190
|
+
# Needs to run through our custom acceptable_pages= method
|
|
191
|
+
self.acceptable_pages = pklas
|
|
192
|
+
else
|
|
193
|
+
log.debug "Session: Already on page"
|
|
194
|
+
end
|
|
183
195
|
end
|
|
184
196
|
|
|
185
197
|
# Wait for long-term AJAX-style processing, i.e. watch the page
|
data/lib/gless/wrap_watir.rb
CHANGED
|
@@ -21,9 +21,10 @@ module Gless
|
|
|
21
21
|
require 'rspec'
|
|
22
22
|
include RSpec::Matchers
|
|
23
23
|
|
|
24
|
-
# @return [Gless::WrapWatir
|
|
24
|
+
# @return [Symbol, Gless::WrapWatir, Watir::Element, Watir::ElementCollection]
|
|
25
|
+
# The symbol for the parent of this element,
|
|
25
26
|
# restricting the scope of its selectorselement.
|
|
26
|
-
attr_accessor :
|
|
27
|
+
attr_accessor :gless_parent
|
|
27
28
|
|
|
28
29
|
# Sets up the wrapping.
|
|
29
30
|
#
|
|
@@ -40,6 +41,7 @@ module Gless
|
|
|
40
41
|
# The wrapper only considers *visible* matching elements, unless
|
|
41
42
|
# the selectors include ":invisible => true".
|
|
42
43
|
#
|
|
44
|
+
# @param [Symbol] name The name of this method; used for debugging.
|
|
43
45
|
# @param [Gless::Browser] browser
|
|
44
46
|
# @param [Gless::Session] session
|
|
45
47
|
# @param [Gless::BasePage] page
|
|
@@ -56,13 +58,18 @@ module Gless
|
|
|
56
58
|
#
|
|
57
59
|
# is the selector arguments.
|
|
58
60
|
# @param [Gless::BasePage, Array<Gless::BasePage>] click_destination Optional. A list of pages that are OK places to end up after we click on this element
|
|
59
|
-
# @param [
|
|
61
|
+
# @param [Symbol] gless_parent The symbol for the parent element under which the wrapped element is restricted.
|
|
62
|
+
# @param [Array<Array<Object>>] child The list of of the children over which
|
|
63
|
+
# element selection is restricted. Each Array contains the symbol of the
|
|
64
|
+
# element and the arguments passed to it.
|
|
60
65
|
# @param [Boolean] cache Whether to cache this element. If false,
|
|
61
66
|
# +find_elem+, unless overridden with its argument, performs a lookup
|
|
62
67
|
# each time it is invoked; otherwise, the watir element is recorded
|
|
63
68
|
# and kept until the session changes the page. If nil, the default value
|
|
64
69
|
# is retrieved from the config.
|
|
65
|
-
|
|
70
|
+
# @param [Boolean] unique Whether to require element matches to be unique.
|
|
71
|
+
def initialize(name, browser, session, page, orig_type, orig_selector_args, click_destination, gless_parent, child, cache, unique, *args)
|
|
72
|
+
@name = name
|
|
66
73
|
@browser = browser
|
|
67
74
|
@session = session
|
|
68
75
|
@page = page
|
|
@@ -71,8 +78,11 @@ module Gless
|
|
|
71
78
|
@num_retries = 3
|
|
72
79
|
@wait_time = 30
|
|
73
80
|
@click_destination = click_destination
|
|
74
|
-
@
|
|
81
|
+
@gless_parent = gless_parent
|
|
82
|
+
@child = child
|
|
75
83
|
@cache = cache.nil? ? @session.get_config(:global, :cache) : cache
|
|
84
|
+
@unique = unique
|
|
85
|
+
@args = [*args]
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
# Finds the element in question; deals with the fact that the
|
|
@@ -85,32 +95,56 @@ module Gless
|
|
|
85
95
|
# just passes those variables to the Watir browser as normal.
|
|
86
96
|
#
|
|
87
97
|
# @param [Boolean] use_cache If not nil, overrides the element's +cache+
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
|
|
98
|
+
# value. If false, the element is re-located; otherwise, if the element
|
|
99
|
+
# has already been found, return it.
|
|
100
|
+
# @param [Boolean] must_exist (true) Assume the element exists; otherwise,
|
|
101
|
+
# return nil if an exception is thrown while locating the element.
|
|
102
|
+
def find_elem use_cache = nil, must_exist = true
|
|
91
103
|
use_cache = @cache if use_cache.nil?
|
|
92
104
|
if use_cache
|
|
93
105
|
@cached_elem ||= find_elem_directly
|
|
94
106
|
else
|
|
95
107
|
@cached_elem = find_elem_directly
|
|
96
108
|
end
|
|
109
|
+
|
|
110
|
+
if @temporary
|
|
111
|
+
e = @cached_elem
|
|
112
|
+
@cached_elem = nil
|
|
113
|
+
e
|
|
114
|
+
else
|
|
115
|
+
@cached_elem
|
|
116
|
+
end
|
|
97
117
|
end
|
|
98
118
|
|
|
99
119
|
# Find the element in question, regardless of whether the element has
|
|
100
120
|
# already been identified. The cache is complete ignored and is not
|
|
101
121
|
# updated. To update the cache and re-locate the element, use +find_elem
|
|
102
122
|
# false+
|
|
103
|
-
|
|
123
|
+
#
|
|
124
|
+
# @param [Boolean] must_exist (true) Assume the element exists; otherwise,
|
|
125
|
+
# return nil if an exception is thrown while locating the element.
|
|
126
|
+
def find_elem_directly must_exist = true
|
|
104
127
|
tries=0
|
|
105
128
|
begin
|
|
106
129
|
# Do we want to return more than one element?
|
|
107
130
|
multiples = false
|
|
108
131
|
|
|
109
|
-
|
|
132
|
+
case gless_parent
|
|
133
|
+
when NilClass
|
|
134
|
+
par = @browser
|
|
135
|
+
when Symbol
|
|
136
|
+
par = @page.send(gless_parent).find_elem
|
|
137
|
+
when WrapWatir
|
|
138
|
+
par = gless_parent.find_elem
|
|
139
|
+
when Watir::Element
|
|
140
|
+
par = gless_parent
|
|
141
|
+
when Watir::ElementCollection
|
|
142
|
+
par = gless_parent
|
|
143
|
+
end
|
|
110
144
|
|
|
111
145
|
if @orig_selector_args.has_key? :proc
|
|
112
146
|
# If it's a Proc, it can handle its own visibility checking
|
|
113
|
-
return @orig_selector_args[:proc].call par
|
|
147
|
+
return @orig_selector_args[:proc].call par, @page, *@args
|
|
114
148
|
else
|
|
115
149
|
# We want all the relevant elements, so force that if it's
|
|
116
150
|
# not what was asked for
|
|
@@ -125,21 +159,50 @@ module Gless
|
|
|
125
159
|
end
|
|
126
160
|
end
|
|
127
161
|
@session.log.debug "WrapWatir: find_elem: elements type: #{type}"
|
|
128
|
-
elems = par.send(type, @orig_selector_args)
|
|
162
|
+
elems = @child.inject(par.send(type, @orig_selector_args)) do |watir_elems, child_gless_elem_arg|
|
|
163
|
+
watir_elems.select do |watir_elem|
|
|
164
|
+
child_watir_elem = @page.send(child_gless_elem_arg[0], *child_gless_elem_arg[1..-1]).with_parent(watir_elem).find_elem nil, false
|
|
165
|
+
if child_watir_elem.nil?
|
|
166
|
+
false
|
|
167
|
+
else
|
|
168
|
+
if child_watir_elem.kind_of? Watir::Element
|
|
169
|
+
child_watir_elem.exists?
|
|
170
|
+
else
|
|
171
|
+
child_watir_elem.length >= 1
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
129
176
|
end
|
|
130
177
|
|
|
131
178
|
@session.log.debug "WrapWatir: find_elem: elements identified by #{trimmed_selectors.inspect} initial version: #{elems.inspect}"
|
|
132
179
|
|
|
133
|
-
if elems
|
|
180
|
+
if @unique && elems && elems.length > 1
|
|
181
|
+
@session.log.debug "WrapWatir: find_elem: '#{@name}' is not unique"
|
|
182
|
+
return par.send(@orig_type, :id => /$^ ('#{@name}' is not unique)/)
|
|
183
|
+
elsif elems.nil? || elems.length == 0
|
|
134
184
|
@session.log.debug "WrapWatir: find_elem: can't find any element identified by #{trimmed_selectors.inspect}"
|
|
135
185
|
# Generally, watir-webdriver code expects *something*
|
|
136
186
|
# back, and uses .present? to see if it's really there, so
|
|
137
187
|
# we get the singleton to satisfy that need.
|
|
138
|
-
|
|
188
|
+
|
|
189
|
+
# Check for non-watir selectors, in which case we approximate by
|
|
190
|
+
# creating a watir-selector that never matches.
|
|
191
|
+
if elems.kind_of? Array
|
|
192
|
+
# Rather than proceeding silently without the non-watir selectors,
|
|
193
|
+
# we return an element that is never present, requiring a new
|
|
194
|
+
# element to be returned.
|
|
195
|
+
#
|
|
196
|
+
# Set temporary to avoid caching this element.
|
|
197
|
+
@temporary = true
|
|
198
|
+
return par.send(@orig_type, :id => /$^ ('#{@name}' not found)/)
|
|
199
|
+
else
|
|
200
|
+
return par.send(@orig_type, @orig_selector_args)
|
|
201
|
+
end
|
|
139
202
|
end
|
|
140
203
|
|
|
141
204
|
# We got something unexpected; just give it back
|
|
142
|
-
if ! elems.is_a?(Watir::ElementCollection)
|
|
205
|
+
if ! elems.is_a?(Watir::ElementCollection) && !elems.is_a?(Array)
|
|
143
206
|
@session.log.debug "WrapWatir: find_elem: elements aren't a collection; returning them"
|
|
144
207
|
return elems
|
|
145
208
|
end
|
|
@@ -147,12 +210,15 @@ module Gless
|
|
|
147
210
|
if multiples
|
|
148
211
|
# We're OK returning the whole set
|
|
149
212
|
@session.log.debug "WrapWatir: find_elem: multiples were requested; returning #{elems.inspect}"
|
|
213
|
+
@session.log.info "WrapWatir: find_elem: #{@name} returns an array due to non-watir element filtering; :proc can be used to override behaviour" if elems.kind_of? Array
|
|
150
214
|
return elems
|
|
151
215
|
elsif elems.length == 1
|
|
152
216
|
# It's not a collection; just return it.
|
|
153
217
|
@session.log.debug "WrapWatir: find_elem: only one item found; returning #{elems[0].inspect}"
|
|
154
218
|
return elems[0]
|
|
155
219
|
else
|
|
220
|
+
return nil if elems.length < 1 unless must_exist
|
|
221
|
+
|
|
156
222
|
unless @orig_selector_args.has_key? :invisible and @orig_selector_args[:invisible]
|
|
157
223
|
if trimmed_selectors.inspect !~ /password/i
|
|
158
224
|
@session.log.debug "WrapWatir: find_elem: elements identified by #{trimmed_selectors.inspect} before visibility selection: #{elems.inspect}"
|
|
@@ -186,14 +252,23 @@ module Gless
|
|
|
186
252
|
|
|
187
253
|
if tries < 3
|
|
188
254
|
@session.log.debug "WrapWatir: find_elem: Retrying after exception."
|
|
255
|
+
tries += 1
|
|
189
256
|
retry
|
|
190
257
|
else
|
|
191
|
-
@session.log.
|
|
258
|
+
@session.log.warn "WrapWatir: find_elem: Giving up after exception."
|
|
259
|
+
raise
|
|
192
260
|
end
|
|
193
|
-
tries += 1
|
|
194
261
|
end
|
|
195
262
|
end
|
|
196
263
|
|
|
264
|
+
# Retrieve a copy of the gless element with a different parent. With a
|
|
265
|
+
# different parent, the copy may refer to a different element.
|
|
266
|
+
def with_parent(gless_parent)
|
|
267
|
+
copy = clone
|
|
268
|
+
copy.gless_parent = gless_parent
|
|
269
|
+
copy
|
|
270
|
+
end
|
|
271
|
+
|
|
197
272
|
# Pulls any procs out of the selectors for debugging purposes
|
|
198
273
|
def trimmed_selectors
|
|
199
274
|
@orig_selector_args.reject { |k,v| k == :proc }
|
|
@@ -201,9 +276,99 @@ module Gless
|
|
|
201
276
|
|
|
202
277
|
# Passes everything through to the underlying Watir object, but
|
|
203
278
|
# with logging.
|
|
204
|
-
|
|
279
|
+
#
|
|
280
|
+
# If caching is enabled, wraps the element to check for stale element
|
|
281
|
+
# reference errors.
|
|
282
|
+
def wrap_watir_call(m, *args, &block)
|
|
205
283
|
wrapper_logging(m, args)
|
|
206
|
-
|
|
284
|
+
if ! @cache
|
|
285
|
+
find_elem.send(m, *args, &block)
|
|
286
|
+
else
|
|
287
|
+
# Caching is enabled for this element. We do some complex processing
|
|
288
|
+
# here to appropriately respond to cases in which caching causes
|
|
289
|
+
# issues, in which case we let the user know by emitting a helpful
|
|
290
|
+
# warning and trying again without caching.
|
|
291
|
+
|
|
292
|
+
# catch_stale takes an WatirElement with a block and tries running the
|
|
293
|
+
# block, trying it again with caching disabled if it fails with a
|
|
294
|
+
# +StaleElementReferenceError+. This exists to avoid repetition in the
|
|
295
|
+
# code.
|
|
296
|
+
catch_stale = -> &block do
|
|
297
|
+
begin
|
|
298
|
+
block.call
|
|
299
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError => e
|
|
300
|
+
warn_prefix = "#{@name}: "
|
|
301
|
+
|
|
302
|
+
# The method call did something directly but raised the relevant
|
|
303
|
+
# exception. Try without caching enabled.
|
|
304
|
+
|
|
305
|
+
@session.log.warn "#{warn_prefix}caught a StaleElementReferenceError; trying without caching..."
|
|
306
|
+
|
|
307
|
+
begin
|
|
308
|
+
r = block.call false
|
|
309
|
+
@session.log.warn "#{warn_prefix}the call succeeded without caching. Please add \":cache => false\" to prevent this from occurring; disabling caching for this elem."
|
|
310
|
+
@cache = false
|
|
311
|
+
r
|
|
312
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError => e
|
|
313
|
+
@session.log.error "#{warn_prefix}disabled caching but caught the same type of exception; failing."
|
|
314
|
+
raise
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Each method call might do something directly to an element, or it
|
|
320
|
+
# might return a child element. In case of the former, we catch
|
|
321
|
+
# +StaleElementReferenceError+s. In the case of the latter, wrap the
|
|
322
|
+
# element.
|
|
323
|
+
wrap_object = -> wrap_meth, *wrap_args, wrap_block, &gen_elem do
|
|
324
|
+
catch_stale.call do |use_cache|
|
|
325
|
+
r = gen_elem.call(use_cache).send(wrap_meth, *wrap_args, &wrap_block)
|
|
326
|
+
|
|
327
|
+
# The call itself succeeded, whether we tried disabling caching or
|
|
328
|
+
# not.
|
|
329
|
+
if ! ((r.kind_of? Watir::Element) || (r.kind_of? Watir::ElementCollection))
|
|
330
|
+
# Safe to return.
|
|
331
|
+
r
|
|
332
|
+
else
|
|
333
|
+
# Wrap the result in a new object that catches stale element
|
|
334
|
+
# reference exceptions and responds appropriately.
|
|
335
|
+
#
|
|
336
|
+
# Using a proxy class as a child of +BasicObject+ would be much
|
|
337
|
+
# simpler, but to avoid problems with code that analyzes the
|
|
338
|
+
# object's class, we have some more work to do.
|
|
339
|
+
#
|
|
340
|
+
# Override each instance method with a wrapper that checks for
|
|
341
|
+
# stale element reference errors, and then similarly override
|
|
342
|
+
# +method_missing+.
|
|
343
|
+
wrapped = r.clone
|
|
344
|
+
wrapped.class.instance_methods.each do |inst_meth|
|
|
345
|
+
if inst_meth != :define_singleton_method
|
|
346
|
+
wrapped.define_singleton_method inst_meth do |*inst_args, &inst_block|
|
|
347
|
+
wrap_object.call(inst_meth, *inst_args, inst_block) {|use_cache| gen_elem.call(use_cache).send(wrap_meth, *wrap_args, &wrap_block)}
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
wrapped.define_singleton_method :method_missing do |inst_meth, *inst_args, &inst_block|
|
|
352
|
+
wrap_object.call(inst_meth, *inst_args, inst_block) {|use_cache| gen_elem.call(use_cache).send(wrap_meth, *wrap_args, &wrap_block)}
|
|
353
|
+
end
|
|
354
|
+
wrapped
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
wrap_object.call(m, *args, block) {|use_cache| find_elem use_cache}
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Passes everything through to the underlying Watir object, first checking
|
|
364
|
+
# whether the method call is the name for another element on the same page,
|
|
365
|
+
# in which case the second element is re-evaluated with a different parent.
|
|
366
|
+
def method_missing(m, *args, &block)
|
|
367
|
+
if @page.class.elements.member? m
|
|
368
|
+
@page.send(m, *args, &block).with_parent self
|
|
369
|
+
else
|
|
370
|
+
wrap_watir_call(m, *args, &block)
|
|
371
|
+
end
|
|
207
372
|
end
|
|
208
373
|
|
|
209
374
|
# Used to log all pass through behaviours. In debug mode,
|
|
@@ -221,7 +386,7 @@ module Gless
|
|
|
221
386
|
|
|
222
387
|
@session.log.debug "WrapWatir: Calling #{m} with arguments #{args.inspect} on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
|
|
223
388
|
|
|
224
|
-
if elem.present? && elem.class.name == 'Watir::HTMLElement'
|
|
389
|
+
if (elem.respond_to? :present?) && elem.present? && elem.class.name == 'Watir::HTMLElement'
|
|
225
390
|
@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}"
|
|
226
391
|
end
|
|
227
392
|
end
|
|
@@ -264,7 +429,7 @@ module Gless
|
|
|
264
429
|
wrapper_logging('click', nil)
|
|
265
430
|
@session.log.debug "WrapWatir: click: Calling click on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
|
|
266
431
|
if elem.exists?
|
|
267
|
-
|
|
432
|
+
wrap_watir_call :click
|
|
268
433
|
end
|
|
269
434
|
if block_given?
|
|
270
435
|
yield
|
|
@@ -277,7 +442,7 @@ module Gless
|
|
|
277
442
|
else
|
|
278
443
|
wrapper_logging('click', nil)
|
|
279
444
|
@session.log.debug "WrapWatir: click: Calling click on a #{elem.class.name} element identified by: #{trimmed_selectors.inspect}"
|
|
280
|
-
|
|
445
|
+
wrap_watir_call :click
|
|
281
446
|
end
|
|
282
447
|
end
|
|
283
448
|
|
|
@@ -309,21 +474,21 @@ module Gless
|
|
|
309
474
|
if elem.class.name == 'Watir::TextField'
|
|
310
475
|
set_text = args[0]
|
|
311
476
|
@session.log.debug "WrapWatir: set: setting text on #{elem.inspect}/#{elem.html} to #{set_text}"
|
|
312
|
-
|
|
477
|
+
wrap_watir_call :set, set_text
|
|
313
478
|
|
|
314
479
|
@num_retries.times do |x|
|
|
315
480
|
@session.log.debug "WrapWatir: Checking that text entry worked"
|
|
316
|
-
if
|
|
481
|
+
if wrap_watir_call(:value) == set_text
|
|
317
482
|
break
|
|
318
483
|
else
|
|
319
484
|
@session.log.debug "WrapWatir: It did not; sleeping for #{@wait_time} seconds"
|
|
320
485
|
sleep @wait_time
|
|
321
486
|
@session.log.debug "WrapWatir: Retrying."
|
|
322
487
|
wrapper_logging('set', set_text)
|
|
323
|
-
|
|
488
|
+
wrap_watir_call :set, set_text
|
|
324
489
|
end
|
|
325
490
|
end
|
|
326
|
-
|
|
491
|
+
wrap_watir_call(:value).to_s.should == set_text.to_s
|
|
327
492
|
@session.log.debug "WrapWatir: The text entry worked"
|
|
328
493
|
|
|
329
494
|
return self
|
|
@@ -331,27 +496,27 @@ module Gless
|
|
|
331
496
|
# Double-check radio buttons
|
|
332
497
|
elsif elem.class.name == 'Watir::Radio'
|
|
333
498
|
wrapper_logging('set', [])
|
|
334
|
-
|
|
499
|
+
wrap_watir_call :set
|
|
335
500
|
|
|
336
501
|
@num_retries.times do |x|
|
|
337
502
|
@session.log.debug "WrapWatir: Checking that the radio selection worked"
|
|
338
|
-
if
|
|
503
|
+
if wrap_watir_call(:set?) == true
|
|
339
504
|
break
|
|
340
505
|
else
|
|
341
506
|
@session.log.debug "WrapWatir: It did not; sleeping for #{@wait_time} seconds"
|
|
342
507
|
sleep @wait_time
|
|
343
508
|
@session.log.debug "WrapWatir: Retrying."
|
|
344
509
|
wrapper_logging('set', [])
|
|
345
|
-
|
|
510
|
+
wrap_watir_call :set
|
|
346
511
|
end
|
|
347
512
|
end
|
|
348
|
-
|
|
513
|
+
wrap_watir_call(:set?).should be_true
|
|
349
514
|
@session.log.debug "WrapWatir: The radio set worked"
|
|
350
515
|
|
|
351
516
|
return self
|
|
352
517
|
|
|
353
518
|
else
|
|
354
|
-
|
|
519
|
+
wrap_watir_call(:set, *args)
|
|
355
520
|
end
|
|
356
521
|
end
|
|
357
522
|
end
|
data/lib/gless.rb
CHANGED
|
@@ -6,10 +6,15 @@
|
|
|
6
6
|
# project.
|
|
7
7
|
module Gless
|
|
8
8
|
# The current version number.
|
|
9
|
-
VERSION = '1.
|
|
9
|
+
VERSION = '1.3.1'
|
|
10
10
|
|
|
11
11
|
# Sets up the config, logger and browser instances, the ordering
|
|
12
|
-
# of which is slightly tricky.
|
|
12
|
+
# of which is slightly tricky. If a block is given, the config, after being
|
|
13
|
+
# initialized from the config file, is passed to the block, which should
|
|
14
|
+
# return the new, updated config.
|
|
15
|
+
#
|
|
16
|
+
# @yield [config] The config loaded from the development file. The
|
|
17
|
+
# optional block should return an updated config if given.
|
|
13
18
|
#
|
|
14
19
|
# @return [Gless::Logger, Gless::EnvConfig, Gless::Browser] logger, config, browser (in that order)
|
|
15
20
|
def self.setup( tag )
|
|
@@ -17,6 +22,7 @@ module Gless
|
|
|
17
22
|
|
|
18
23
|
# Create the config reading/storage object
|
|
19
24
|
config = Gless::EnvConfig.new( )
|
|
25
|
+
config = yield config if block_given?
|
|
20
26
|
|
|
21
27
|
# Get the whole backtrace, please.
|
|
22
28
|
if config.get :global, :debug
|
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.
|
|
4
|
+
version: 1.3.1
|
|
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-
|
|
12
|
+
date: 2013-08-21 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rspec
|