testable 0.3.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|