testable 0.3.0 → 0.4.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 +33 -25
- data/.hound.yml +21 -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 +12 -25
- data/Rakefile +20 -11
- data/bin/console +2 -2
- 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 +67 -0
- data/examples/testable-watir.rb +118 -0
- data/lib/testable/attribute.rb +38 -0
- data/lib/testable/context.rb +72 -0
- data/lib/testable/element.rb +162 -31
- data/lib/testable/errors.rb +6 -2
- data/lib/testable/extensions/data_setter.rb +109 -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/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/lib/testable.rb +37 -10
- data/testable.gemspec +16 -8
- metadata +70 -18
- 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,73 @@
|
|
1
|
+
module Watir
|
2
|
+
class Element
|
3
|
+
OBSERVER_FILE = "/dom_observer.js".freeze
|
4
|
+
DOM_OBSERVER = File.read("#{File.dirname(__FILE__)}#{OBSERVER_FILE}").freeze
|
5
|
+
|
6
|
+
# This method makes a call to `execute_async_script` which means that the
|
7
|
+
# DOM observer script must explicitly signal that it is finished by
|
8
|
+
# invoking a callback. In this case, the callback is nothing more than
|
9
|
+
# a delay. The delay is being used to allow the DOM to be updated before
|
10
|
+
# script actions continue.
|
11
|
+
#
|
12
|
+
# The method returns true if the DOM has been changed within the element
|
13
|
+
# context, while false means that the DOM has not yet finished changing.
|
14
|
+
# Note the wording: "has not finished changing." It's known that the DOM
|
15
|
+
# is changing because the observer has recognized that. So the question
|
16
|
+
# this method is helping to answer is "has it finished?"
|
17
|
+
#
|
18
|
+
# Consider the following element definition:
|
19
|
+
#
|
20
|
+
# p :page_list, id: 'navlist'
|
21
|
+
#
|
22
|
+
# You could then do this:
|
23
|
+
#
|
24
|
+
# page_list.dom_updated?
|
25
|
+
#
|
26
|
+
# That would return true if the DOM content for page_list has finished
|
27
|
+
# updating. If the DOM was in the process of being updated, this would
|
28
|
+
# return false. You could also do this:
|
29
|
+
#
|
30
|
+
# page_list.wait_until(&:dom_updated?).click
|
31
|
+
#
|
32
|
+
# This will use Watir's wait until functionality to wait for the DOM to
|
33
|
+
# be updated within the context of the element. Note that the "&:" is
|
34
|
+
# that the object that `dom_updated?` is being called on (in this case
|
35
|
+
# `page_list`) substitutes the ampersand. You can also structure it like
|
36
|
+
# this:
|
37
|
+
#
|
38
|
+
# page_list.wait_until do |element|
|
39
|
+
# element.dom_updated?
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# The default delay of waiting for the DOM to start updating is 1.1
|
43
|
+
# second. However, you can pass a delay value when you call the method
|
44
|
+
# to set your own value, which can be useful for particular sensitivities
|
45
|
+
# in the application you are testing.
|
46
|
+
def dom_updated?(delay: 1.1)
|
47
|
+
element_call do
|
48
|
+
begin
|
49
|
+
driver.manage.timeouts.script_timeout = delay + 1
|
50
|
+
driver.execute_async_script(DOM_OBSERVER, wd, delay)
|
51
|
+
rescue Selenium::WebDriver::Error::JavascriptError => e
|
52
|
+
# This situation can occur if the script execution has started before
|
53
|
+
# a new page is fully loaded. The specific error being checked for
|
54
|
+
# here is one that occurs when a new page is loaded as that page is
|
55
|
+
# trying to execute a JavaScript function.
|
56
|
+
retry if e.message.include?(
|
57
|
+
'document unloaded while waiting for result'
|
58
|
+
)
|
59
|
+
raise
|
60
|
+
ensure
|
61
|
+
# Note that this setting here means any user-defined timeout would
|
62
|
+
# effectively be overwritten.
|
63
|
+
driver.manage.timeouts.script_timeout = 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
alias dom_has_updated? dom_updated?
|
69
|
+
alias dom_has_changed? dom_updated?
|
70
|
+
alias when_dom_updated dom_updated?
|
71
|
+
alias when_dom_changed dom_updated?
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Testable
|
2
|
+
module Element
|
3
|
+
module Locator
|
4
|
+
private
|
5
|
+
|
6
|
+
# This method is what actually calls the browser instance to find
|
7
|
+
# an element. If there is an element definition like this:
|
8
|
+
#
|
9
|
+
# text_field :username, id: 'username'
|
10
|
+
#
|
11
|
+
# This will become the following:
|
12
|
+
#
|
13
|
+
# browser.text_field(id: 'username')
|
14
|
+
#
|
15
|
+
# Note that the `to_subtype` method is called, which allows for the
|
16
|
+
# generic `element` to be expressed as the type of element, as opposed
|
17
|
+
# to `text_field` or `select_list`. For example, an `element` may be
|
18
|
+
# defined like this:
|
19
|
+
#
|
20
|
+
# element :enable, id: 'enableForm'
|
21
|
+
#
|
22
|
+
# Which means it would look like this:
|
23
|
+
#
|
24
|
+
# Watir::HTMLElement:0x1c8c9 selector={:id=>"enableForm"}
|
25
|
+
#
|
26
|
+
# Whereas getting the subtype would give you:
|
27
|
+
#
|
28
|
+
# Watir::CheckBox:0x12f8b elector={element: (webdriver element)}
|
29
|
+
#
|
30
|
+
# Which is what you would get if the element definition were this:
|
31
|
+
#
|
32
|
+
# checkbox :enable, id: 'enableForm'
|
33
|
+
#
|
34
|
+
# Using the subtype does get tricky for scripts that require the
|
35
|
+
# built-in sychronization aspects and wait states of Watir.
|
36
|
+
#
|
37
|
+
# The approach being used in this method is necessary to allow actions
|
38
|
+
# like `set`, which are not available on `element`, even though other
|
39
|
+
# actions, like `click`, are. But if you use `element` for your element
|
40
|
+
# definitions, and your script requires a series of actions where elements
|
41
|
+
# may be delayed in appearing, you'll get an "unable to locate element"
|
42
|
+
# message, along with a Watir::Exception::UnknownObjectException.
|
43
|
+
#
|
44
|
+
# A check is made if an UnknownObjectException occurs due to a ready
|
45
|
+
# validation. That's necessary because a ready validation has to find
|
46
|
+
# an element in order to determine the ready state, but that element
|
47
|
+
# might not be present.
|
48
|
+
def access_element(element, locators, _qualifiers)
|
49
|
+
if element == "element".to_sym
|
50
|
+
@browser.element(locators).to_subtype
|
51
|
+
else
|
52
|
+
@browser.__send__(element, locators)
|
53
|
+
end
|
54
|
+
rescue Watir::Exception::UnknownObjectException
|
55
|
+
return false if caller_locations.any? do |str|
|
56
|
+
str.to_s.match?("ready_validations_pass?")
|
57
|
+
end
|
58
|
+
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require "testable/situation"
|
2
|
+
|
3
|
+
module Testable
|
4
|
+
module Pages
|
5
|
+
include Situation
|
6
|
+
|
7
|
+
# This provides a list of the methods that are defined on the page
|
8
|
+
# definition. This is helpful is you need to query the page to see
|
9
|
+
# if an element has been provided since all elements automatically
|
10
|
+
# become methods on a definition instance.
|
11
|
+
def definition_api
|
12
|
+
public_methods(false) - Object.public_methods
|
13
|
+
end
|
14
|
+
|
15
|
+
# The `visit` method provides navigation to a specific page by passing
|
16
|
+
# in the URL. If no URL is passed in, this method will attempt to use
|
17
|
+
# the `url_is` attribute from the interface it is being called on.
|
18
|
+
def visit(url = nil, &block)
|
19
|
+
no_url_provided if url.nil? && url_attribute.nil?
|
20
|
+
@browser.goto(url) unless url.nil?
|
21
|
+
@browser.goto(url_attribute) if url.nil?
|
22
|
+
when_ready(&block) if block_given?
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
alias view visit
|
27
|
+
alias navigate_to visit
|
28
|
+
alias goto visit
|
29
|
+
alias perform visit
|
30
|
+
|
31
|
+
# A call to `url_attribute` returns what the value of the `url_is`
|
32
|
+
# attribute is for the given interface. It's important to note that
|
33
|
+
# this is not grabbing the URL that is displayed in the browser;
|
34
|
+
# rather it's the one declared in the interface definition, if any.
|
35
|
+
def url_attribute
|
36
|
+
self.class.url_attribute
|
37
|
+
end
|
38
|
+
|
39
|
+
# A call to `url` returns the actual URL of the page that is displayed
|
40
|
+
# in the browser.
|
41
|
+
def url
|
42
|
+
@browser.url
|
43
|
+
end
|
44
|
+
|
45
|
+
alias page_url url
|
46
|
+
alias current_url url
|
47
|
+
|
48
|
+
# A call to `url_match_attribute` returns what the value of the
|
49
|
+
# `url_matches` attribute is for the given interface. It's important
|
50
|
+
# to note that the URL matching mechanism is effectively a regular
|
51
|
+
# expression check.
|
52
|
+
def url_match_attribute
|
53
|
+
value = self.class.url_match_attribute
|
54
|
+
return if value.nil?
|
55
|
+
|
56
|
+
value = Regexp.new(value) unless value.is_a?(Regexp)
|
57
|
+
value
|
58
|
+
end
|
59
|
+
|
60
|
+
# A call to `has_correct_url?`returns true or false if the actual URL
|
61
|
+
# found in the browser matches the `url_matches` assertion. This is
|
62
|
+
# important to note. It's not using the `url_is` attribute nor the URL
|
63
|
+
# displayed in the browser. It's using the `url_matches` attribute.
|
64
|
+
def has_correct_url?
|
65
|
+
no_url_match_is_possible if url_attribute.nil? && url_match_attribute.nil?
|
66
|
+
!(url =~ url_match_attribute).nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
alias displayed? has_correct_url?
|
70
|
+
alias loaded? has_correct_url?
|
71
|
+
|
72
|
+
# A call to `title_attribute` returns what the value of the `title_is`
|
73
|
+
# attribute is for the given definition. It's important to note that
|
74
|
+
# this is not grabbing the title that is displayed in the browser;
|
75
|
+
# rather it's the one declared in the interface definition, if any.
|
76
|
+
def title_attribute
|
77
|
+
self.class.title_attribute
|
78
|
+
end
|
79
|
+
|
80
|
+
# A call to `title` returns the actual title of the page that is
|
81
|
+
# displayed in the browser.
|
82
|
+
def title
|
83
|
+
@browser.title
|
84
|
+
end
|
85
|
+
|
86
|
+
alias page_title title
|
87
|
+
|
88
|
+
# A call to `has_correct_title?` returns true or false if the actual
|
89
|
+
# title of the current page in the browser matches the `title_is`
|
90
|
+
# attribute. Notice that this check is done as part of a match rather
|
91
|
+
# than a direct check. This allows for regular expressions to be used.
|
92
|
+
def has_correct_title?
|
93
|
+
no_title_is_provided if title_attribute.nil?
|
94
|
+
!title.match(title_attribute).nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
# A call to `secure?` returns true if the page is secure and false
|
98
|
+
# otherwise. This is a simple check that looks for whether or not the
|
99
|
+
# current URL begins with 'https'.
|
100
|
+
def secure?
|
101
|
+
!url.match(/^https/).nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
# A call to `markup` returns all markup on a page. Generally you don't
|
105
|
+
# just want the entire markup but rather want to parse the output of
|
106
|
+
# the `markup` call.
|
107
|
+
def markup
|
108
|
+
browser.html
|
109
|
+
end
|
110
|
+
|
111
|
+
alias html markup
|
112
|
+
|
113
|
+
# A call to `text` returns all text on a page. Note that this is text
|
114
|
+
# that is taken out of the markup context. It is unlikely you will just
|
115
|
+
# want the entire text but rather want to parse the output of the
|
116
|
+
# `text` call.
|
117
|
+
def text
|
118
|
+
browser.text
|
119
|
+
end
|
120
|
+
|
121
|
+
alias page_text text
|
122
|
+
|
123
|
+
# This method provides a call to the synchronous `execute_script`
|
124
|
+
# action on the browser, passing in JavaScript that you want to have
|
125
|
+
# executed against the current page. For example:
|
126
|
+
#
|
127
|
+
# result = page.run_script("alert('Cogent ran a script.')")
|
128
|
+
#
|
129
|
+
# You can also run full JavaScript snippets.
|
130
|
+
#
|
131
|
+
# script = <<-JS
|
132
|
+
# return arguments[0].innerHTML
|
133
|
+
# JS
|
134
|
+
#
|
135
|
+
# page.run_script(script, page.account)
|
136
|
+
#
|
137
|
+
# Here you pass two arguments to `run_script`. One is the script itself
|
138
|
+
# and the other are some arguments that you want to pass as part of
|
139
|
+
# of the execution. In this case, an element definition (`account`) is
|
140
|
+
# being passed in.
|
141
|
+
def run_script(script, *args)
|
142
|
+
browser.execute_script(script, *args)
|
143
|
+
end
|
144
|
+
|
145
|
+
alias execute_script run_script
|
146
|
+
|
147
|
+
# A call to `screen_width` returns the width of the browser screen as
|
148
|
+
# reported by the browser API, using a JavaScript call to the `screen`
|
149
|
+
# object.
|
150
|
+
def screen_width
|
151
|
+
run_script("return screen.width;")
|
152
|
+
end
|
153
|
+
|
154
|
+
# A call to `screen_height` returns the height of the browser screen as
|
155
|
+
# reported by the browser API, using a JavaScript call to the `screen`
|
156
|
+
# object.
|
157
|
+
def screen_height
|
158
|
+
run_script("return screen.height;")
|
159
|
+
end
|
160
|
+
|
161
|
+
# This method provides a means to maximize the browser window. This
|
162
|
+
# is done by getting the screen width and height via JavaScript calls.
|
163
|
+
def maximize
|
164
|
+
browser.window.resize_to(screen_width, screen_height)
|
165
|
+
end
|
166
|
+
|
167
|
+
# This method provides a call to the browser window to resize that
|
168
|
+
# window to the specified width and height values.
|
169
|
+
def resize(width, height)
|
170
|
+
browser.window.resize_to(width, height)
|
171
|
+
end
|
172
|
+
|
173
|
+
# This method provides a call to the browser window to move the
|
174
|
+
# window to the specified x and y screen coordinates.
|
175
|
+
def move_to(x_coord, y_coord)
|
176
|
+
browser.window.move_to(x_coord, y_coord)
|
177
|
+
end
|
178
|
+
|
179
|
+
alias resize_to resize
|
180
|
+
|
181
|
+
# This method sends a standard "browser refresh" message to the browser.
|
182
|
+
def refresh
|
183
|
+
browser.refresh
|
184
|
+
end
|
185
|
+
|
186
|
+
alias refresh_page refresh
|
187
|
+
|
188
|
+
# A call to `get_cookie` allows you to specify a particular cookie, by
|
189
|
+
# name, and return the information specified in the cookie.
|
190
|
+
def get_cookie(name)
|
191
|
+
browser.cookies.to_a.each do |cookie|
|
192
|
+
return cookie[:value] if cookie[:name] == name
|
193
|
+
end
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
197
|
+
# A call to `clear_cookies` removes all the cookies from the current
|
198
|
+
# instance of the browser that is being controlled by WebDriver.
|
199
|
+
def clear_cookies
|
200
|
+
browser.cookies.clear
|
201
|
+
end
|
202
|
+
|
203
|
+
alias remove_cookies clear_cookies
|
204
|
+
|
205
|
+
# A call to `screenshot` saves a screenshot of the current browser
|
206
|
+
# page. Note that this will grab the entire browser page, even portions
|
207
|
+
# of it that are off panel and need to be scrolled to. You can pass in
|
208
|
+
# the path and filename of the image that you want the screenshot
|
209
|
+
# saved to.
|
210
|
+
def screenshot(file)
|
211
|
+
browser.screenshot.save(file)
|
212
|
+
end
|
213
|
+
|
214
|
+
alias save_screenshot screenshot
|
215
|
+
end
|
216
|
+
end
|
data/lib/testable/ready.rb
CHANGED
@@ -4,7 +4,15 @@ module Testable
|
|
4
4
|
module Ready
|
5
5
|
include Situation
|
6
6
|
|
7
|
-
|
7
|
+
# The ReadyAttributes contains methods that can be called directly on the
|
8
|
+
# interface class definition. These are very much like the attributes
|
9
|
+
# that are used for defining aspects of the pages, such as `url_is` or
|
10
|
+
# `title_is`. These attributes are included separately so as to maintain
|
11
|
+
# more modularity.
|
12
|
+
module ReadyAttributes
|
13
|
+
# This method will provide a list of the ready_validations that have
|
14
|
+
# been defined. This list will contain the list in the order that the
|
15
|
+
# validations were defined in.
|
8
16
|
def ready_validations
|
9
17
|
if superclass.respond_to?(:ready_validations)
|
10
18
|
superclass.ready_validations + _ready_validations
|
@@ -13,41 +21,75 @@ module Testable
|
|
13
21
|
end
|
14
22
|
end
|
15
23
|
|
24
|
+
# When this attribute method is specified on an interface, it will
|
25
|
+
# append the validation provided by the block.
|
16
26
|
def page_ready(&block)
|
17
27
|
_ready_validations << block
|
18
28
|
end
|
19
29
|
|
20
30
|
alias page_ready_when page_ready
|
21
31
|
|
32
|
+
private
|
33
|
+
|
22
34
|
def _ready_validations
|
23
35
|
@_ready_validations ||= []
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
27
|
-
attr_accessor :ready, :ready_error
|
28
|
-
|
29
39
|
def self.included(caller)
|
30
|
-
caller.extend(
|
40
|
+
caller.extend(ReadyAttributes)
|
31
41
|
end
|
32
42
|
|
33
|
-
|
43
|
+
# If a ready validation fails, the message reported by that failure will
|
44
|
+
# be captured in the `ready_error` accessor.
|
45
|
+
attr_accessor :ready, :ready_error
|
46
|
+
|
47
|
+
# The `when_ready` method is called on an instance of an interface. This
|
48
|
+
# executes the provided validation block after the page has been loaded.
|
49
|
+
# The Ready object instance is yielded into the block.
|
50
|
+
#
|
51
|
+
# Calls to the `ready?` method use a poor-man's cache approach. The idea
|
52
|
+
# here being that when a page has confirmed that it is ready, meaning that
|
53
|
+
# no ready validations have failed, that information is stored so that any
|
54
|
+
# subsequent calls to `ready?` do not query the ready validations again.
|
55
|
+
def when_ready(simple_check = false, &_block)
|
34
56
|
already_marked_ready = ready
|
35
57
|
|
36
|
-
|
58
|
+
unless simple_check
|
59
|
+
no_ready_check_possible unless block_given?
|
60
|
+
end
|
37
61
|
|
38
62
|
self.ready = ready?
|
63
|
+
|
39
64
|
not_ready_validation(ready_error || 'NO REASON PROVIDED') unless ready
|
40
|
-
yield self
|
65
|
+
yield self if block_given?
|
41
66
|
ensure
|
42
67
|
self.ready = already_marked_ready
|
43
68
|
end
|
44
69
|
|
70
|
+
def check_if_ready
|
71
|
+
when_ready(true)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The `ready?` method is used to check if the page has been loaded
|
75
|
+
# successfully, which means that none of the ready validations have
|
76
|
+
# indicated failure.
|
77
|
+
#
|
78
|
+
# When `ready?` is called, the blocks that were stored in the above
|
79
|
+
# `ready_validations` array are instance evaluated against the current
|
80
|
+
# page instance.
|
45
81
|
def ready?
|
46
82
|
self.ready_error = nil
|
47
83
|
return true if ready
|
84
|
+
|
48
85
|
ready_validations_pass?
|
49
86
|
end
|
50
87
|
|
88
|
+
private
|
89
|
+
|
90
|
+
# This method checks if the ready validations that have been specified
|
91
|
+
# have passed. If any ready validation fails, no matter if others have
|
92
|
+
# succeeded, this method immediately returns false.
|
51
93
|
def ready_validations_pass?
|
52
94
|
self.class.ready_validations.all? do |validation|
|
53
95
|
passed, message = instance_eval(&validation)
|
data/lib/testable/situation.rb
CHANGED
@@ -20,13 +20,6 @@ module Testable
|
|
20
20
|
raise Testable::Errors::NoUrlForDefinition
|
21
21
|
end
|
22
22
|
|
23
|
-
def url_match_is_empty
|
24
|
-
puts "PROBLEM: url_matches attribute empty.\n" \
|
25
|
-
"The url_matches attribute is empty on the definition " \
|
26
|
-
"'#{retrieve_class(caller)}'.\n\n"
|
27
|
-
raise Testable::Errors::NoUrlMatchForDefinition
|
28
|
-
end
|
29
|
-
|
30
23
|
def no_url_match_is_possible
|
31
24
|
puts "PROBLEM: No url_is or url_matches attribute.\n" \
|
32
25
|
"You called a '#{retrieve_method(caller)}' action but the " \
|
@@ -35,6 +28,13 @@ module Testable
|
|
35
28
|
raise Testable::Errors::NoUrlMatchPossible
|
36
29
|
end
|
37
30
|
|
31
|
+
def url_match_is_empty
|
32
|
+
puts "PROBLEM: url_matches attribute empty.\n" \
|
33
|
+
"The url_matches attribute is empty on the definition " \
|
34
|
+
"'#{retrieve_class(caller)}'.\n\n"
|
35
|
+
raise Testable::Errors::NoUrlMatchForDefinition
|
36
|
+
end
|
37
|
+
|
38
38
|
def title_is_empty
|
39
39
|
puts "PROBLEM: title_is attribute empty.\n" \
|
40
40
|
"The title_is attribute is empty on the definition " \
|
@@ -49,13 +49,6 @@ module Testable
|
|
49
49
|
raise Testable::Errors::NoTitleForDefinition
|
50
50
|
end
|
51
51
|
|
52
|
-
def no_locator(definition, identifier)
|
53
|
-
puts "PROBLEM: No locator provided.\n" \
|
54
|
-
"You are using '#{identifier}' on '#{definition}'. " \
|
55
|
-
"But there is no locator provided with it.\n\n"
|
56
|
-
raise Testable::Errors::NoLocatorForDefinition
|
57
|
-
end
|
58
|
-
|
59
52
|
def not_ready_validation(message)
|
60
53
|
puts "PROBLEM: A ready validation error was encountered.\n" \
|
61
54
|
"A ready validation failed to validate. The ready check was " \
|
@@ -65,24 +58,12 @@ module Testable
|
|
65
58
|
raise Testable::Errors::PageNotValidatedError, message
|
66
59
|
end
|
67
60
|
|
68
|
-
def no_ready_check_possible
|
69
|
-
puts "PROBLEM: A when ready call has no action.\n" \
|
70
|
-
"You called a when_ready on a definition but did not provide " \
|
71
|
-
"any action for it. Add a block with logic that should be " \
|
72
|
-
"executed if the ready check passes.\n\n"
|
73
|
-
raise Testable::Errors::NoBlockForWhenReady
|
74
|
-
end
|
75
|
-
|
76
|
-
def retrieve_class(caller)
|
77
|
-
caller[1][/`.*'/][8..-3]
|
78
|
-
end
|
79
|
-
|
80
61
|
def retrieve_method(caller)
|
81
62
|
caller[0][/`.*'/][1..-2]
|
82
63
|
end
|
83
64
|
|
84
|
-
def
|
85
|
-
|
65
|
+
def retrieve_class(caller)
|
66
|
+
caller[1][/`.*'/][8..-3]
|
86
67
|
end
|
87
68
|
end
|
88
69
|
end
|
data/lib/testable/version.rb
CHANGED
@@ -1,24 +1,25 @@
|
|
1
1
|
module Testable
|
2
2
|
module_function
|
3
3
|
|
4
|
-
VERSION = "0.
|
4
|
+
VERSION = "0.4.0".freeze
|
5
5
|
|
6
6
|
def version
|
7
7
|
"""
|
8
8
|
Testable v#{Testable::VERSION}
|
9
9
|
watir: #{gem_version('watir')}
|
10
10
|
selenium-webdriver: #{gem_version('selenium-webdriver')}
|
11
|
+
capybara: #{gem_version('capybara')}
|
11
12
|
"""
|
12
13
|
end
|
13
14
|
|
14
|
-
def dependencies
|
15
|
-
Gem.loaded_specs.values.map { |spec| "#{spec.name} #{spec.version}\n" }
|
16
|
-
.uniq.sort.join(",").split(",")
|
17
|
-
end
|
18
|
-
|
19
15
|
def gem_version(name)
|
20
16
|
Gem.loaded_specs[name].version
|
21
17
|
rescue NoMethodError
|
22
18
|
puts "No gem loaded for #{name}."
|
23
19
|
end
|
20
|
+
|
21
|
+
def dependencies
|
22
|
+
Gem.loaded_specs.values.map { |spec| "#{spec.name} #{spec.version}\n" }
|
23
|
+
.uniq.sort.join(",").split(",")
|
24
|
+
end
|
24
25
|
end
|
data/lib/testable.rb
CHANGED
@@ -1,34 +1,55 @@
|
|
1
1
|
require "testable/version"
|
2
|
-
|
2
|
+
require "testable/page"
|
3
3
|
require "testable/ready"
|
4
|
-
require "testable/
|
4
|
+
require "testable/context"
|
5
5
|
require "testable/element"
|
6
|
-
require "testable/
|
7
|
-
require "testable/
|
8
|
-
|
6
|
+
require "testable/locator"
|
7
|
+
require "testable/attribute"
|
8
|
+
|
9
|
+
require "testable/extensions/data_setter"
|
10
|
+
require "testable/extensions/dom_observer"
|
9
11
|
|
10
12
|
require "watir"
|
11
|
-
require "
|
13
|
+
require "capybara"
|
14
|
+
require "webdrivers"
|
12
15
|
|
13
16
|
module Testable
|
14
17
|
def self.included(caller)
|
15
|
-
caller.extend Testable::
|
16
|
-
caller.extend Testable::
|
18
|
+
caller.extend Testable::Pages::Attribute
|
19
|
+
caller.extend Testable::Pages::Element
|
17
20
|
caller.__send__ :include, Testable::Ready
|
18
|
-
caller.__send__ :include, Testable::
|
19
|
-
caller.__send__ :include, Testable::Interface::Page
|
21
|
+
caller.__send__ :include, Testable::Pages
|
20
22
|
caller.__send__ :include, Testable::Element::Locator
|
23
|
+
caller.__send__ :include, Testable::DataSetter
|
21
24
|
end
|
22
25
|
|
23
26
|
def initialize(browser = nil, &block)
|
24
27
|
@browser = Testable.browser unless Testable.browser.nil?
|
25
28
|
@browser = browser if Testable.browser.nil?
|
29
|
+
begin_with if respond_to?(:begin_with)
|
26
30
|
instance_eval(&block) if block
|
27
31
|
end
|
28
32
|
|
33
|
+
# This accessor is needed so that internal API calls, like `markup` or
|
34
|
+
# `text`, have access to the browser instance. This is also necessary
|
35
|
+
# in order for element handling to be called appropriately on the a
|
36
|
+
# valid browser instance. This is an instance-level access to whatever
|
37
|
+
# browser Testable is using.
|
29
38
|
attr_accessor :browser
|
30
39
|
|
31
40
|
class << self
|
41
|
+
def watir_api
|
42
|
+
browser.methods - Object.public_methods -
|
43
|
+
Watir::Container.instance_methods
|
44
|
+
end
|
45
|
+
|
46
|
+
def selenium_api
|
47
|
+
browser.driver.methods - Object.public_methods
|
48
|
+
end
|
49
|
+
|
50
|
+
# This accessor is needed so that Testable itself can provide a browser
|
51
|
+
# reference to indicate connection to WebDriver. This is a class-level
|
52
|
+
# access to the browser.
|
32
53
|
attr_accessor :browser
|
33
54
|
|
34
55
|
def set_browser(app = :chrome, *args)
|
@@ -36,8 +57,14 @@ module Testable
|
|
36
57
|
Testable.browser = @browser
|
37
58
|
end
|
38
59
|
|
60
|
+
alias start_browser set_browser
|
61
|
+
|
39
62
|
def quit_browser
|
40
63
|
@browser.quit
|
41
64
|
end
|
65
|
+
|
66
|
+
def api
|
67
|
+
methods - Object.public_methods
|
68
|
+
end
|
42
69
|
end
|
43
70
|
end
|
data/testable.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "testable/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = "testable"
|
@@ -9,25 +8,34 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ["Jeff Nyman"]
|
10
9
|
spec.email = ["jeffnyman@gmail.com"]
|
11
10
|
|
12
|
-
spec.summary = %q{
|
11
|
+
spec.summary = %q{Web and API Automation, using Capybara and Watir}
|
13
12
|
spec.description = %q{Provides a semantic DSL to construct fluent interfaces for test execution logic.}
|
14
13
|
spec.homepage = "https://github.com/jeffnyman/testable"
|
15
14
|
spec.license = "MIT"
|
16
15
|
|
17
|
-
spec.
|
18
|
-
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/jeffnyman/testable"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/jeffnyman/testable/blob/master/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
24
|
end
|
20
25
|
spec.bindir = "exe"
|
21
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
27
|
spec.require_paths = ["lib"]
|
23
28
|
|
24
|
-
spec.add_development_dependency "bundler", "~>
|
29
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
25
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
26
31
|
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency "simplecov"
|
27
33
|
spec.add_development_dependency "rubocop"
|
28
34
|
spec.add_development_dependency "pry"
|
29
35
|
|
30
|
-
spec.add_runtime_dependency "watir", "~> 6.
|
36
|
+
spec.add_runtime_dependency "watir", ["~> 6.4"]
|
37
|
+
spec.add_runtime_dependency "capybara", ["~> 3.0"]
|
38
|
+
spec.add_runtime_dependency "webdrivers", ["~> 4.0"]
|
31
39
|
|
32
40
|
spec.post_install_message = %{
|
33
41
|
(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)
|