testable 0.3.0 → 0.8.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.
- checksums.yaml +5 -5
- data/.gitignore +37 -25
- data/.hound.yml +31 -12
- data/.rubocop.yml +4 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +3 -1
- data/{LICENSE.txt → LICENSE.md} +2 -2
- data/README.md +36 -17
- data/Rakefile +52 -11
- data/bin/console +2 -2
- data/bin/setup +0 -0
- data/examples/testable-capybara-context.rb +64 -0
- data/examples/testable-capybara-rspec.rb +70 -0
- data/examples/testable-capybara.rb +46 -0
- data/examples/testable-info.rb +65 -0
- data/examples/testable-watir-context.rb +67 -0
- data/examples/testable-watir-datasetter.rb +52 -0
- data/examples/testable-watir-events.rb +44 -0
- data/examples/testable-watir-ready.rb +34 -0
- data/examples/testable-watir-test.rb +80 -0
- data/examples/testable-watir.rb +118 -0
- data/lib/testable.rb +142 -10
- data/lib/testable/attribute.rb +38 -0
- data/lib/testable/capybara/dsl.rb +82 -0
- data/lib/testable/capybara/node.rb +30 -0
- data/lib/testable/capybara/page.rb +29 -0
- data/lib/testable/context.rb +73 -0
- data/lib/testable/deprecator.rb +40 -0
- data/lib/testable/element.rb +162 -31
- data/lib/testable/errors.rb +6 -2
- data/lib/testable/extensions/core_ruby.rb +13 -0
- data/lib/testable/extensions/data_setter.rb +144 -0
- data/lib/testable/extensions/dom_observer.js +58 -4
- data/lib/testable/extensions/dom_observer.rb +73 -0
- data/lib/testable/locator.rb +63 -0
- data/lib/testable/logger.rb +16 -0
- data/lib/testable/page.rb +216 -0
- data/lib/testable/ready.rb +49 -7
- data/lib/testable/situation.rb +9 -28
- data/lib/testable/version.rb +7 -6
- data/testable.gemspec +19 -9
- metadata +90 -23
- data/circle.yml +0 -3
- data/lib/testable/data_setter.rb +0 -51
- data/lib/testable/dom_update.rb +0 -19
- data/lib/testable/factory.rb +0 -27
- data/lib/testable/interface.rb +0 -114
@@ -0,0 +1,29 @@
|
|
1
|
+
require "capybara"
|
2
|
+
require "testable/capybara/node"
|
3
|
+
|
4
|
+
module Testable
|
5
|
+
class Page < Node
|
6
|
+
# The `Page` class wraps an HTML page with an application-specific API.
|
7
|
+
# This can be extended to define an API for manipulating the pages of
|
8
|
+
# the web application.
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def self.visit
|
12
|
+
new.visit
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(node: Capybara.current_session, path: nil)
|
16
|
+
@node = node
|
17
|
+
@path = path
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit
|
21
|
+
@node.visit(path)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def current?
|
26
|
+
@node.current_path == path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Testable
|
2
|
+
module Context
|
3
|
+
# Creates a definition context for actions and establishes the context
|
4
|
+
# for execution. Given an interface definition for a page like this:
|
5
|
+
#
|
6
|
+
# class TestPage
|
7
|
+
# include Testable
|
8
|
+
#
|
9
|
+
# url_is "http://localhost:9292"
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# You can do the following:
|
13
|
+
#
|
14
|
+
# on_visit(TestPage)
|
15
|
+
def on_visit(definition, &block)
|
16
|
+
create_active(definition)
|
17
|
+
@context.visit
|
18
|
+
verify_page(@context)
|
19
|
+
call_block(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a definition context for actions. If an existing context
|
23
|
+
# exists, that context will be re-used. You can use this simply to keep
|
24
|
+
# the context for a script clear. For example, say you have the following
|
25
|
+
# interface definitions for pages:
|
26
|
+
#
|
27
|
+
# class Home
|
28
|
+
# include Testable
|
29
|
+
# url_is "http://localhost:9292"
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class Navigation
|
33
|
+
# include Testable
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# You could then do this:
|
37
|
+
#
|
38
|
+
# on_visit(Home)
|
39
|
+
# on(Navigation)
|
40
|
+
#
|
41
|
+
# The Home definition needs the url_is attribute in order for the on_view
|
42
|
+
# factory to work. But Navigation does not because the `on` method is not
|
43
|
+
# attempting to visit, simply to reference.
|
44
|
+
def on(definition, &block)
|
45
|
+
create_active(definition)
|
46
|
+
call_block(&block)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# This method is used to provide a means for checking if a page has been
|
52
|
+
# navigated to correctly as part of a context. This is useful because
|
53
|
+
# the context signature should remain highly readable, and checks for
|
54
|
+
# whether a given page has been reached would make the context definition
|
55
|
+
# look sloppy.
|
56
|
+
def verify_page(context)
|
57
|
+
return unless defined?(context.url_match_attribute)
|
58
|
+
return if context.url_match_attribute.nil?
|
59
|
+
return if context.has_correct_url?
|
60
|
+
|
61
|
+
raise Testable::Errors::PageURLFromFactoryNotVerified
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_active(definition)
|
65
|
+
@context = definition.new unless @context.is_a?(definition)
|
66
|
+
end
|
67
|
+
|
68
|
+
def call_block(&block)
|
69
|
+
yield @context if block
|
70
|
+
@context
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Testable
|
2
|
+
class Deprecator
|
3
|
+
class << self
|
4
|
+
def deprecate(current, upcoming = nil, known_version = nil)
|
5
|
+
if upcoming
|
6
|
+
warn(
|
7
|
+
"#{current} is being deprecated and should no longer be used. \
|
8
|
+
Use #{upcoming} instead."
|
9
|
+
)
|
10
|
+
else
|
11
|
+
warn("#{current} is being deprecated and should no longer be used.")
|
12
|
+
end
|
13
|
+
|
14
|
+
warn(
|
15
|
+
"#{current} will be removed in Testable #{known_version}."
|
16
|
+
) if known_version
|
17
|
+
end
|
18
|
+
|
19
|
+
def soft_deprecate(current, reason, known_version, upcoming = nil)
|
20
|
+
debug("The #{current} method is changing and is now configurable.")
|
21
|
+
debug("REASON: #{reason}.")
|
22
|
+
debug(
|
23
|
+
"Moving forwards into Testable #{known_version}, \
|
24
|
+
the default behavior will change."
|
25
|
+
)
|
26
|
+
debug("It is advised that you change to using #{upcoming}") if upcoming
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def warn(message)
|
32
|
+
Testable.logger.warn(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug(message)
|
36
|
+
Testable.logger.debug(message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/testable/element.rb
CHANGED
@@ -1,14 +1,9 @@
|
|
1
1
|
require "watir"
|
2
|
-
require "testable/situation"
|
3
2
|
|
4
3
|
module Testable
|
5
|
-
include Situation
|
6
|
-
|
7
4
|
module_function
|
8
5
|
|
9
|
-
|
10
|
-
@elements = Watir::Container.instance_methods unless @elements
|
11
|
-
end
|
6
|
+
NATIVE_QUALIFIERS = %i[visible].freeze
|
12
7
|
|
13
8
|
def elements?
|
14
9
|
@elements
|
@@ -18,38 +13,174 @@ module Testable
|
|
18
13
|
@elements.include? method.to_sym
|
19
14
|
end
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
16
|
+
def elements
|
17
|
+
@elements ||= Watir::Container.instance_methods unless @elements
|
18
|
+
end
|
19
|
+
|
20
|
+
module Pages
|
21
|
+
module Element
|
22
|
+
# This iterator goes through the Watir container methods and
|
23
|
+
# provides a method for each so that Watir-based element names
|
24
|
+
# cane be defined on an interface definition, as part of an
|
25
|
+
# element definition.
|
26
|
+
Testable.elements.each do |element|
|
27
|
+
define_method(element) do |*signature, &block|
|
28
|
+
identifier, signature = parse_signature(signature)
|
29
|
+
context = context_from_signature(signature, &block)
|
30
|
+
define_element_accessor(identifier, signature, element, &context)
|
31
|
+
end
|
28
32
|
end
|
29
|
-
end
|
30
33
|
|
31
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
# A "signature" consists of a full element definition. For example:
|
37
|
+
#
|
38
|
+
# text_field :username, id: 'username'
|
39
|
+
#
|
40
|
+
# The signature of this element definition is:
|
41
|
+
#
|
42
|
+
# [:username, {:id=>"username"}]
|
43
|
+
#
|
44
|
+
# This is the identifier of the element (`username`) and the locator
|
45
|
+
# provided for it. This method separates out the identifier and the
|
46
|
+
# locator.
|
47
|
+
def parse_signature(signature)
|
48
|
+
[signature.shift, signature.shift]
|
49
|
+
end
|
32
50
|
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
51
|
+
# Returns the block or proc that serves as a context for an element
|
52
|
+
# definition. Consider the following element definitions:
|
53
|
+
#
|
54
|
+
# ul :facts, id: 'fact-list'
|
55
|
+
# span :fact, -> { facts.span(class: 'site-item')}
|
56
|
+
#
|
57
|
+
# Here the second element definition provides a proc that contains a
|
58
|
+
# context for another element definition. That leads to the following
|
59
|
+
# construction being sent to the browser:
|
60
|
+
#
|
61
|
+
# @browser.ul(id: 'fact-list').span(class: 'site-item')
|
62
|
+
def context_from_signature(*signature, &block)
|
63
|
+
if block_given?
|
64
|
+
block
|
65
|
+
else
|
66
|
+
context = signature.shift
|
67
|
+
context.is_a?(Proc) && signature.empty? ? context : nil
|
68
|
+
end
|
39
69
|
end
|
40
|
-
end
|
41
70
|
|
42
|
-
|
43
|
-
|
44
|
-
|
71
|
+
# This method provides the means to get the aspects of an accessor
|
72
|
+
# signature. The "aspects" refer to the locator information and any
|
73
|
+
# qualifier information that was provided along with the locator.
|
74
|
+
# This is important because the qualifier is not used to locate an
|
75
|
+
# element but rather to put conditions on how the state of the
|
76
|
+
# element is checked as it is being looked for.
|
77
|
+
#
|
78
|
+
# Note that "qualifiers" here refers to Watir boolean methods.
|
79
|
+
def accessor_aspects(element, *signature)
|
80
|
+
identifier = signature.shift
|
81
|
+
locator_args = {}
|
82
|
+
qualifier_args = {}
|
83
|
+
gather_aspects(identifier, element, locator_args, qualifier_args)
|
84
|
+
[locator_args, qualifier_args]
|
85
|
+
end
|
45
86
|
|
46
|
-
|
47
|
-
|
87
|
+
# This method is used to separate the two aspects of an accessor --
|
88
|
+
# the locators and the qualifiers. Part of this process involves
|
89
|
+
# querying the Watir driver library to determine what qualifiers
|
90
|
+
# it handles natively. Consider the following:
|
91
|
+
#
|
92
|
+
# select_list :accounts, id: 'accounts', selected: 'Select Option'
|
93
|
+
#
|
94
|
+
# Given that, this method will return with the following:
|
95
|
+
#
|
96
|
+
# locator_args: {:id=>"accounts"}
|
97
|
+
# qualifier_args: {:selected=>"Select Option"}
|
98
|
+
#
|
99
|
+
# Consider this:
|
100
|
+
#
|
101
|
+
# p :login_form, id: 'open', index: 0, visible: true
|
102
|
+
#
|
103
|
+
# Given that, this method will return with the following:
|
104
|
+
#
|
105
|
+
# locator_args: {:id=>"open", :index=>0, :visible=>true}
|
106
|
+
# qualifier_args: {}
|
107
|
+
#
|
108
|
+
# Notice that the `visible` qualifier is part of the locator arguments
|
109
|
+
# as opposed to being a qualifier argument, like `selected` was in the
|
110
|
+
# previous example. This is because Watir 6.x handles the `visible`
|
111
|
+
# qualifier natively. "Handling natively" means that when a qualifier
|
112
|
+
# is part of the locator, Watir knows how to intrpret the qualifier
|
113
|
+
# as a condition on the element, not as a way to locate the element.
|
114
|
+
def gather_aspects(identifier, element, locator_args, qualifier_args)
|
115
|
+
identifier.each_with_index do |hashes, index|
|
116
|
+
next if hashes.nil? || hashes.is_a?(Proc)
|
117
|
+
|
118
|
+
hashes.each do |k, v|
|
119
|
+
methods = Watir.element_class_for(element).instance_methods
|
120
|
+
if methods.include?(:"#{k}?") && !NATIVE_QUALIFIERS.include?(k)
|
121
|
+
qualifier_args[k] = identifier[index][k]
|
122
|
+
else
|
123
|
+
locator_args[k] = v
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
[locator_args, qualifier_args]
|
128
|
+
end
|
48
129
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
130
|
+
# Defines an accessor method for an element that allows the "friendly
|
131
|
+
# name" (identifier) of the element to be proxied to a Watir element
|
132
|
+
# object that corresponds to the element type. When this identifier
|
133
|
+
# is referenced, it generates an accessor method for that element
|
134
|
+
# in the browser. Consider this element definition defined on a class
|
135
|
+
# with an instance of `page`:
|
136
|
+
#
|
137
|
+
# text_field :username, id: 'username'
|
138
|
+
#
|
139
|
+
# This allows:
|
140
|
+
#
|
141
|
+
# page.username.set 'tester'
|
142
|
+
#
|
143
|
+
# So any element identifier can be called as if it were a method on
|
144
|
+
# the interface (class) on which it is defined. Because the method
|
145
|
+
# is proxied to Watir, you can use the full Watir API by calling
|
146
|
+
# methods (like `set`, `click`, etc) on the element identifier.
|
147
|
+
#
|
148
|
+
# It is also possible to have an element definition like this:
|
149
|
+
#
|
150
|
+
# text_field :password
|
151
|
+
#
|
152
|
+
# This would allow access like this:
|
153
|
+
#
|
154
|
+
# page.username(id: 'username').set 'tester'
|
155
|
+
#
|
156
|
+
# This approach would lead to the *values variable having an array
|
157
|
+
# like this: [{:id => 'username'}].
|
158
|
+
#
|
159
|
+
# A third approach would be to utilize one element definition within
|
160
|
+
# the context of another. Consider the following element definitions:
|
161
|
+
#
|
162
|
+
# article :practice, id: 'practice'
|
163
|
+
#
|
164
|
+
# a :page_link do |text|
|
165
|
+
# practice.a(text: text)
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# This would allow access like this:
|
169
|
+
#
|
170
|
+
# page.page_link('Drag and Drop').click
|
171
|
+
#
|
172
|
+
# This approach would lead to the *values variable having an array
|
173
|
+
# like this: ["Drag and Drop"].
|
174
|
+
def define_element_accessor(identifier, *signature, element, &block)
|
175
|
+
locators, qualifiers = accessor_aspects(element, signature)
|
176
|
+
define_method(identifier.to_s) do |*values|
|
177
|
+
if block_given?
|
178
|
+
instance_exec(*values, &block)
|
179
|
+
else
|
180
|
+
locators = values[0] if locators.empty?
|
181
|
+
access_element(element, locators, qualifiers)
|
182
|
+
end
|
183
|
+
end
|
53
184
|
end
|
54
185
|
end
|
55
186
|
end
|
data/lib/testable/errors.rb
CHANGED
@@ -4,8 +4,12 @@ module Testable
|
|
4
4
|
NoUrlMatchForDefinition = Class.new(StandardError)
|
5
5
|
NoUrlMatchPossible = Class.new(StandardError)
|
6
6
|
NoTitleForDefinition = Class.new(StandardError)
|
7
|
-
NoLocatorForDefinition = Class.new(StandardError)
|
8
7
|
PageNotValidatedError = Class.new(StandardError)
|
9
|
-
|
8
|
+
|
9
|
+
class PageURLFromFactoryNotVerified < StandardError
|
10
|
+
def message
|
11
|
+
"The page URL was not verified during a factory setup."
|
12
|
+
end
|
13
|
+
end
|
10
14
|
end
|
11
15
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class String
|
2
|
+
# This is only required if using a version of Ruby before 2.4. A match?
|
3
|
+
# method for String was added in version 2.4.
|
4
|
+
def match?(string, pos = 0)
|
5
|
+
!!match(string, pos)
|
6
|
+
end unless //.respond_to?(:match?)
|
7
|
+
end
|
8
|
+
|
9
|
+
class FalseClass
|
10
|
+
def exists?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class Object
|
2
|
+
# This method is necessary to dynamically chain method calls. The reason
|
3
|
+
# this is necessary the data setter initially has no idea of the actual
|
4
|
+
# object it's going to be dealing with, particularly because part of its
|
5
|
+
# job is to find that object and map a data string to it. Not only this,
|
6
|
+
# but that element will have been called on a specific instance of a
|
7
|
+
# interface class. With the example provide in the comments below for the
|
8
|
+
# `using` method, the following would be the case:
|
9
|
+
#
|
10
|
+
# method_chain: warp_factor.set
|
11
|
+
# o (object): <WarpTravel:0x007f8b23224218>
|
12
|
+
# m (method): warp_factor
|
13
|
+
# data: 1
|
14
|
+
#
|
15
|
+
# Thus what you end up with is:
|
16
|
+
#
|
17
|
+
# <WarpTravel:0x007f8b23224218>.warp_factor.set 1
|
18
|
+
def chain(method_chain, data = nil)
|
19
|
+
return self if method_chain.empty?
|
20
|
+
|
21
|
+
method_chain.split('.').inject(self) do |o, m|
|
22
|
+
if data.nil?
|
23
|
+
o.send(m.intern)
|
24
|
+
else
|
25
|
+
o.send(m.intern, data)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Testable
|
32
|
+
module DataSetter
|
33
|
+
# The `using` method tells Testable to match up whatever data is passed
|
34
|
+
# in via the action with element definitions. If those elements are found,
|
35
|
+
# they will be populated with the specified data. Consider the following:
|
36
|
+
#
|
37
|
+
# class WarpTravel
|
38
|
+
# include Testable
|
39
|
+
#
|
40
|
+
# text_field :warp_factor, id: 'warpInput'
|
41
|
+
# text_field :velocity, id: 'velocityInput'
|
42
|
+
# text_field :distance, id: 'distInput'
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# Assuming an instance of this class called `page`, you could do the
|
46
|
+
# following:
|
47
|
+
#
|
48
|
+
# page.using_data(warp_factor: 1, velocity: 1, distance: 4.3)
|
49
|
+
#
|
50
|
+
# This is based on conventions. The idea is that element definitions are
|
51
|
+
# written in the form of "snake case" -- meaning, underscores between
|
52
|
+
# each separate word. In the above example, "warp_factor: 1" would be
|
53
|
+
# matched to the `warp_factor` element and the value used for that
|
54
|
+
# element would be "1". The default operation for a text field is to
|
55
|
+
# enter the value in. It is also possible to use strings:
|
56
|
+
#
|
57
|
+
# page.using_data("warp factor": 1, velocity: 1, distance: 4.3)
|
58
|
+
#
|
59
|
+
# Here "warp factor" would be converted to "warp_factor".
|
60
|
+
def using(data)
|
61
|
+
data.each do |key, value|
|
62
|
+
use_data_with(key, value.to_s) if object_enabled_for(key)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias using_data using
|
67
|
+
alias use_data using
|
68
|
+
alias using_values using
|
69
|
+
alias use_values using
|
70
|
+
alias use using
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# This is the method that is delegated to in order to make sure that
|
75
|
+
# elements are interacted with appropriately. This will in turn delegate
|
76
|
+
# to `set_and_select` and `check_and_uncheck`, which determines what
|
77
|
+
# actions are viable based on the type of element that is being dealt
|
78
|
+
# with. These aspects are what tie this particular implementation to
|
79
|
+
# Watir.
|
80
|
+
def use_data_with(key, value)
|
81
|
+
value = preprocess_value(value, key)
|
82
|
+
|
83
|
+
element = send(key.to_s.tr(' ', '_'))
|
84
|
+
set_and_select(key, element, value)
|
85
|
+
check_and_uncheck(key, element, value)
|
86
|
+
click(key, element)
|
87
|
+
end
|
88
|
+
|
89
|
+
# rubocop:disable Metrics/AbcSize
|
90
|
+
# rubocop:disable Metrics/MethodLength
|
91
|
+
def preprocess_value(value, key)
|
92
|
+
return value unless value =~ /\(\(.*\)\)/
|
93
|
+
|
94
|
+
starter = value.index("((")
|
95
|
+
ender = value.index("))")
|
96
|
+
qualifier = value[starter + 2, ender - starter - 2]
|
97
|
+
|
98
|
+
if qualifier == "random_large"
|
99
|
+
value[starter..ender + 1] = rand(1_000_000_000_000).to_s
|
100
|
+
elsif qualifier == "random_ssn"
|
101
|
+
value = rand(9**9).to_s.rjust(9, '0')
|
102
|
+
value.insert 5, "-"
|
103
|
+
value.insert 3, "-"
|
104
|
+
elsif qualifier == "random_selection"
|
105
|
+
list = chain("#{key}.options.to_a")
|
106
|
+
|
107
|
+
selected = list.sample.text
|
108
|
+
selected = list.sample.text if selected.nil?
|
109
|
+
value = selected
|
110
|
+
end
|
111
|
+
|
112
|
+
value
|
113
|
+
end
|
114
|
+
# rubocop:enable Metrics/AbcSize
|
115
|
+
# rubocop:enable Metrics/MethodLength
|
116
|
+
|
117
|
+
def set_and_select(key, element, value)
|
118
|
+
key = key.to_s.tr(' ', '_')
|
119
|
+
chain("#{key}.set", value) if element.class == Watir::TextField
|
120
|
+
chain("#{key}.set") if element.class == Watir::Radio
|
121
|
+
chain("#{key}.select", value) if element.class == Watir::Select
|
122
|
+
end
|
123
|
+
|
124
|
+
def check_and_uncheck(key, element, value)
|
125
|
+
key = key.to_s.tr(' ', '_')
|
126
|
+
return chain("#{key}.check") if element.class == Watir::CheckBox && value
|
127
|
+
|
128
|
+
chain("#{key}.uncheck") if element.class == Watir::CheckBox
|
129
|
+
end
|
130
|
+
|
131
|
+
def click(key, element)
|
132
|
+
chain("#{key}.click") if element.class == Watir::Label
|
133
|
+
end
|
134
|
+
|
135
|
+
# This is a sanity check method to make sure that whatever element is
|
136
|
+
# being used as part of the data setting, it exists in the DOM, is
|
137
|
+
# visible (meaning, display is not 'none'), and is capable of accepting
|
138
|
+
# input, thus being enabled.
|
139
|
+
def object_enabled_for(key)
|
140
|
+
web_element = send(key.to_s.tr(' ', '_'))
|
141
|
+
web_element.enabled? && web_element.present?
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|