ae_page_objects 2.1.0 → 3.0.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 +4 -4
- data/lib/ae_page_objects.rb +58 -13
- data/lib/ae_page_objects/core/dsl.rb +15 -7
- data/lib/ae_page_objects/document_loader.rb +3 -11
- data/lib/ae_page_objects/element.rb +8 -8
- data/lib/ae_page_objects/element_proxy.rb +1 -9
- data/lib/ae_page_objects/exceptions.rb +9 -12
- data/lib/ae_page_objects/node.rb +19 -7
- data/lib/ae_page_objects/single_window/same_window_loader_strategy.rb +1 -1
- data/lib/ae_page_objects/util/wait_time_manager.rb +20 -0
- data/lib/ae_page_objects/version.rb +1 -1
- metadata +3 -3
- data/lib/ae_page_objects/util/page_polling.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79847ab6fa6a51c16ec5127b80cc6e42035abfde
|
4
|
+
data.tar.gz: d20bbebe4ef811ccf92b75e15a3231957fd37fe1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38aa636f72ece06a5c8722085e898736bc57db7b362c2f54752ec9878a585d8e9d10e6dddd090457ce0778e0909704a33b3e3daa654070345a13092845aa5feb
|
7
|
+
data.tar.gz: e75ab259fc8c46c28c1416622af05f6e65081e4f647e9a481c41463c30ae27a9c0267cc834c4e45212aa75b6eaceee5c40cdaaaaed7518ec1bb29d2ba35f013e
|
data/lib/ae_page_objects.rb
CHANGED
@@ -3,7 +3,7 @@ require 'capybara/dsl'
|
|
3
3
|
|
4
4
|
require 'ae_page_objects/version'
|
5
5
|
require 'ae_page_objects/exceptions'
|
6
|
-
require 'ae_page_objects/util/
|
6
|
+
require 'ae_page_objects/util/wait_time_manager'
|
7
7
|
|
8
8
|
module AePageObjects
|
9
9
|
autoload :Node, 'ae_page_objects/node'
|
@@ -16,8 +16,6 @@ module AePageObjects
|
|
16
16
|
autoload :Checkbox, 'ae_page_objects/elements/checkbox'
|
17
17
|
|
18
18
|
class << self
|
19
|
-
include PagePolling
|
20
|
-
|
21
19
|
attr_accessor :default_router
|
22
20
|
|
23
21
|
def browser
|
@@ -35,22 +33,69 @@ module AePageObjects
|
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
|
-
def wait_until(seconds_to_wait = nil, error_message = nil)
|
39
|
-
|
40
|
-
|
36
|
+
def wait_until(seconds_to_wait = nil, error_message = nil, &block)
|
37
|
+
@wait_until ||= 0
|
38
|
+
@wait_until += 1
|
41
39
|
|
42
|
-
|
43
|
-
delay = seconds_to_wait - (Time.now - start_time)
|
40
|
+
result = nil
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
if @wait_until > 1
|
43
|
+
# We want to ensure that only the top-level wait_until does the waiting error handling,
|
44
|
+
# which allows correct timing and best chance of recovering from an error.
|
45
|
+
result = call_wait_until_block(error_message, &block)
|
46
|
+
else
|
47
|
+
seconds_to_wait ||= default_max_wait_time
|
48
|
+
start_time = Time.now
|
49
|
+
|
50
|
+
# In an effort to avoid flakiness, Capybara waits, rescues errors, reloads nodes, and
|
51
|
+
# retries.
|
52
|
+
#
|
53
|
+
# There are cases when Capybara will rescue an error and either nodes are not
|
54
|
+
# reloadable or the DOM has changed in a such a way that no amount of reloading will
|
55
|
+
# help (but perhaps a retry at the higher level may have a chance of success). This leads
|
56
|
+
# to us needless waiting for a long time just to fail.
|
57
|
+
#
|
58
|
+
# There are also cases when Selenium will take such a long time to respond with an error
|
59
|
+
# that Capybara's timeout will be exceeded and no reloading / retrying will occur. Instead,
|
60
|
+
# Capabara will just raise the error.
|
61
|
+
#
|
62
|
+
# In order to combat the two cases, we start with a lower Capybara wait time and increase
|
63
|
+
# it each iteration. This logic is encapsulated in a little utility class.
|
64
|
+
time_manager = WaitTimeManager.new(1.0, seconds_to_wait)
|
65
|
+
begin
|
66
|
+
result = time_manager.using_wait_time { call_wait_until_block(error_message, &block) }
|
67
|
+
rescue => e
|
68
|
+
errors = Capybara.current_session.driver.invalid_element_errors
|
69
|
+
errors += [Capybara::ElementNotFound]
|
70
|
+
errors += [DocumentLoadError, LoadingElementFailed, LoadingPageFailed]
|
71
|
+
errors += [WaitTimeoutError]
|
72
|
+
raise e unless errors.include?(e.class)
|
73
|
+
|
74
|
+
delay = seconds_to_wait - (Time.now - start_time)
|
75
|
+
|
76
|
+
if delay <= 0
|
77
|
+
# Raising the WaitTimeoutError in the rescue block ensures that Ruby attaches
|
78
|
+
# the original exception as the cause for our WaitTimeoutError.
|
79
|
+
raise WaitTimeoutError, e.message
|
80
|
+
end
|
48
81
|
|
49
|
-
|
50
|
-
|
82
|
+
sleep(0.05)
|
83
|
+
raise FrozenInTime, "Time appears to be frozen" if Time.now == start_time
|
84
|
+
|
85
|
+
retry
|
86
|
+
end
|
51
87
|
end
|
52
88
|
|
53
89
|
result
|
90
|
+
ensure
|
91
|
+
@wait_until -= 1
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def call_wait_until_block(error_message, &block)
|
97
|
+
result = block.call
|
98
|
+
result ? result : raise(WaitTimeoutError, error_message || "Timed out waiting for condition")
|
54
99
|
end
|
55
100
|
|
56
101
|
def default_max_wait_time
|
@@ -6,13 +6,21 @@ module AePageObjects
|
|
6
6
|
include InternalHelpers
|
7
7
|
|
8
8
|
def inherited(subclass)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
super
|
10
|
+
|
11
|
+
subclass.is_loaded_blocks.push(*is_loaded_blocks)
|
12
|
+
end
|
13
|
+
|
14
|
+
def element_attributes
|
15
|
+
@element_attributes ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_loaded_blocks
|
19
|
+
@is_loaded_blocks ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def is_loaded(&block)
|
23
|
+
self.is_loaded_blocks << block
|
16
24
|
end
|
17
25
|
|
18
26
|
def element(name, options = {}, &block)
|
@@ -1,9 +1,5 @@
|
|
1
|
-
require 'ae_page_objects/util/page_polling'
|
2
|
-
|
3
1
|
module AePageObjects
|
4
2
|
class DocumentLoader
|
5
|
-
include AePageObjects::PagePolling
|
6
|
-
|
7
3
|
def initialize(query, strategy)
|
8
4
|
@query = query
|
9
5
|
@strategy = strategy
|
@@ -11,14 +7,10 @@ module AePageObjects
|
|
11
7
|
|
12
8
|
def load
|
13
9
|
begin
|
14
|
-
|
10
|
+
AePageObjects.wait_until do
|
15
11
|
@query.conditions.each do |document_condition|
|
16
|
-
|
17
|
-
|
18
|
-
return document
|
19
|
-
end
|
20
|
-
rescue => e
|
21
|
-
raise unless catch_poll_util_error?(e)
|
12
|
+
if document = @strategy.load_document_with_condition(document_condition)
|
13
|
+
return document
|
22
14
|
end
|
23
15
|
end
|
24
16
|
|
@@ -95,15 +95,15 @@ module AePageObjects
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def scoped_node
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
98
|
+
locator = eval_locator(@locator)
|
99
|
+
if locator.empty?
|
100
|
+
parent.node
|
101
|
+
else
|
102
|
+
node = AePageObjects.wait_until { parent.node.first(*locator) }
|
103
|
+
node.allow_reload!
|
104
|
+
node
|
103
105
|
end
|
104
|
-
|
105
|
-
parent.node
|
106
|
-
rescue Capybara::ElementNotFound => e
|
106
|
+
rescue AePageObjects::WaitTimeoutError => e
|
107
107
|
raise LoadingElementFailed, e.message
|
108
108
|
end
|
109
109
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'ae_page_objects/util/page_polling'
|
2
|
-
|
3
1
|
module AePageObjects
|
4
2
|
class ElementProxy
|
5
3
|
# Remove all instance methods so even things like class()
|
@@ -10,8 +8,6 @@ module AePageObjects
|
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
13
|
-
include AePageObjects::PagePolling
|
14
|
-
|
15
11
|
def initialize(element_class, *args)
|
16
12
|
@element_class = element_class
|
17
13
|
@args = args
|
@@ -121,16 +117,12 @@ module AePageObjects
|
|
121
117
|
|
122
118
|
def reload_element
|
123
119
|
@loaded_element = load_element
|
124
|
-
|
125
|
-
true
|
126
120
|
rescue LoadingElementFailed
|
127
121
|
@loaded_element = nil
|
128
|
-
|
129
|
-
true
|
130
122
|
end
|
131
123
|
|
132
124
|
def with_reloaded_element(timeout)
|
133
|
-
|
125
|
+
AePageObjects.wait_until(timeout) do
|
134
126
|
reload_element
|
135
127
|
yield
|
136
128
|
end
|
@@ -10,34 +10,31 @@ module AePageObjects
|
|
10
10
|
class LoadingElementFailed < Error
|
11
11
|
end
|
12
12
|
|
13
|
-
class
|
14
|
-
end
|
15
|
-
|
16
|
-
class ElementNotVisible < ElementExpectationError
|
13
|
+
class PathNotResolvable < Error
|
17
14
|
end
|
18
15
|
|
19
|
-
class
|
16
|
+
class DocumentLoadError < Error
|
20
17
|
end
|
21
18
|
|
22
|
-
class
|
19
|
+
class CastError < Error
|
23
20
|
end
|
24
21
|
|
25
|
-
class
|
22
|
+
class WindowNotFound < Error
|
26
23
|
end
|
27
24
|
|
28
|
-
class
|
25
|
+
class WaitTimeoutError < Error
|
29
26
|
end
|
30
27
|
|
31
|
-
class
|
28
|
+
class ElementNotVisible < WaitTimeoutError
|
32
29
|
end
|
33
30
|
|
34
|
-
class
|
31
|
+
class ElementNotHidden < WaitTimeoutError
|
35
32
|
end
|
36
33
|
|
37
|
-
class
|
34
|
+
class ElementNotPresent < WaitTimeoutError
|
38
35
|
end
|
39
36
|
|
40
|
-
class
|
37
|
+
class ElementNotAbsent < WaitTimeoutError
|
41
38
|
end
|
42
39
|
|
43
40
|
class FrozenInTime < Error
|
data/lib/ae_page_objects/node.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ae_page_objects/core/dsl'
|
2
|
+
require 'ae_page_objects/element_proxy'
|
2
3
|
|
3
4
|
module AePageObjects
|
4
5
|
class Node
|
@@ -14,6 +15,14 @@ module AePageObjects
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
18
|
+
is_loaded do
|
19
|
+
if locator = loaded_locator
|
20
|
+
node.first(*eval_locator(locator)) != nil
|
21
|
+
else
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
17
26
|
def initialize(capybara_node)
|
18
27
|
@node = capybara_node
|
19
28
|
@stale = false
|
@@ -49,7 +58,7 @@ module AePageObjects
|
|
49
58
|
self.class.current_url_without_params
|
50
59
|
end
|
51
60
|
|
52
|
-
METHODS_TO_DELEGATE_TO_NODE = [:
|
61
|
+
METHODS_TO_DELEGATE_TO_NODE = [:value, :set, :text, :visible?]
|
53
62
|
METHODS_TO_DELEGATE_TO_NODE.each do |m|
|
54
63
|
class_eval <<-RUBY
|
55
64
|
def #{m}(*args, &block)
|
@@ -88,13 +97,16 @@ module AePageObjects
|
|
88
97
|
end
|
89
98
|
|
90
99
|
def ensure_loaded!
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
self
|
96
|
-
rescue Capybara::ElementNotFound => e
|
100
|
+
AePageObjects.wait_until { is_loaded? }
|
101
|
+
rescue AePageObjects::WaitTimeoutError => e
|
97
102
|
raise LoadingElementFailed, e.message
|
98
103
|
end
|
104
|
+
|
105
|
+
# This should not block and instead attempt to return immediately (e.g. use #all / #first
|
106
|
+
# instead of #find / #has_selector ). Unfortunately, this is difficult to enforce since even
|
107
|
+
# with #all / #first capyabara may wait.
|
108
|
+
def is_loaded?
|
109
|
+
self.class.is_loaded_blocks.all? { |block| self.instance_eval(&block) }
|
110
|
+
end
|
99
111
|
end
|
100
112
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module AePageObjects
|
2
|
+
class WaitTimeManager
|
3
|
+
def initialize(min_time, max_time)
|
4
|
+
@wait_time = min_time
|
5
|
+
@max_time = max_time
|
6
|
+
end
|
7
|
+
|
8
|
+
def using_wait_time
|
9
|
+
start_time = Time.now
|
10
|
+
@wait_time = [@wait_time, @max_time].min
|
11
|
+
Capybara.using_wait_time(@wait_time) do
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
ensure
|
15
|
+
if Time.now - start_time > @wait_time
|
16
|
+
@wait_time *= 2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ae_page_objects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Donnie Tognazzini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -58,7 +58,7 @@ files:
|
|
58
58
|
- lib/ae_page_objects/single_window/window.rb
|
59
59
|
- lib/ae_page_objects/util/hash_symbolizer.rb
|
60
60
|
- lib/ae_page_objects/util/internal_helpers.rb
|
61
|
-
- lib/ae_page_objects/util/
|
61
|
+
- lib/ae_page_objects/util/wait_time_manager.rb
|
62
62
|
- lib/ae_page_objects/version.rb
|
63
63
|
homepage: http://github.com/appfolio/ae_page_objects
|
64
64
|
licenses:
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module AePageObjects
|
2
|
-
module PagePolling
|
3
|
-
# Quickly polls the block until it returns (as opposed to throwing an exception). Using poll
|
4
|
-
# is a safer alternative to,
|
5
|
-
#
|
6
|
-
# Capybara.using_wait_time(0) do
|
7
|
-
# has_content?('Admin')
|
8
|
-
# end
|
9
|
-
#
|
10
|
-
# where we want to determine whether 'Admin' is on the page (without waiting for it to appear).
|
11
|
-
# This is often used to switch login / functionality of page objects based on the state of the
|
12
|
-
# page.
|
13
|
-
#
|
14
|
-
# With poll, the above patterns becomes,
|
15
|
-
#
|
16
|
-
# AePageObjects.poll do
|
17
|
-
# has_content?('Admin')
|
18
|
-
# end
|
19
|
-
def poll(seconds_to_wait = nil, &block)
|
20
|
-
result = nil
|
21
|
-
poll_until(seconds_to_wait) do
|
22
|
-
result = block.call
|
23
|
-
true
|
24
|
-
end
|
25
|
-
result
|
26
|
-
end
|
27
|
-
|
28
|
-
# Quickly polls the block until it returns something truthy. This is a helper function for
|
29
|
-
# special cases and probably NOT what you want to use. See AePageObjects#wait_until or
|
30
|
-
# #poll.
|
31
|
-
def poll_until(seconds_to_wait = nil, &block)
|
32
|
-
AePageObjects.wait_until(seconds_to_wait) do
|
33
|
-
# Capybara normally catches errors and retries. However, with the wait time of zero,
|
34
|
-
# capybara catches the errors and immediately reraises them. So we have to catch
|
35
|
-
# those errors in the similar fashion to capybara such that we properly can wait the
|
36
|
-
# whole seconds_to_wait.
|
37
|
-
begin
|
38
|
-
Capybara.using_wait_time(0, &block)
|
39
|
-
rescue => e
|
40
|
-
raise unless catch_poll_until_error?(e)
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def catch_poll_until_error?(error)
|
47
|
-
types = Capybara.current_session.driver.invalid_element_errors + [Capybara::ElementNotFound]
|
48
|
-
types.any? do |type|
|
49
|
-
error.is_a?(type)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|