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