Skip to content

Commit

Permalink
Merge pull request #78 from appfolio/fixElementProxyNotPresent
Browse files Browse the repository at this point in the history
ElementProxy enhancements and fix for #65
  • Loading branch information
dtognazzini committed Oct 4, 2014
2 parents 36609be + 2f4284f commit 941181d
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 23 deletions.
66 changes: 55 additions & 11 deletions lib/ae_page_objects/element_proxy.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'timeout'

module AePageObjects
class ElementProxy

Expand All @@ -14,6 +12,8 @@ class ElementProxy
def initialize(element_class, *args)
@element_class = element_class
@args = args

@loaded_element = nil
end

# Provided so that visible? can be asked without
Expand All @@ -33,21 +33,45 @@ def not_visible?
end

def present?
! presence.nil?
wait_for_presence
true
rescue ElementNotPresent
false
end

def not_present?
Waiter.wait_for do
! present?
end
wait_for_absence
true
rescue ElementNotAbsent
false
end

def presence
element
rescue AePageObjects::LoadingElementFailed
implicit_element
rescue LoadingElementFailed
nil
end

def wait_for_presence(timeout = nil)
is_present = Waiter.wait_for(timeout) do
! presence.nil?
end

unless is_present
raise ElementNotPresent, "element_class: #{@element_class}, options: #{@options.inspect}"
end
end

def wait_for_absence(timeout = nil)
is_absent = Waiter.wait_for(timeout) do
check_absence
end

unless is_absent
raise ElementNotAbsent, "element_class: #{@element_class}, options: #{@options.inspect}"
end
end

def is_a?(type)
type == @element_class || type == ElementProxy
end
Expand All @@ -61,7 +85,7 @@ def method_missing(name, *args, &block)
return @element_class
end

element.__send__(name, *args, &block)
implicit_element.__send__(name, *args, &block)
end

def respond_to?(*args)
Expand All @@ -70,8 +94,28 @@ def respond_to?(*args)

private

def element
@element ||= @element_class.new(*@args)
def load_element
@element_class.new(*@args)
end

def implicit_element
@loaded_element ||= load_element
end

def check_absence
load_element

false
rescue LoadingElementFailed
true
rescue => e
if Capybara.current_session.driver.is_a?(Capybara::Selenium::Driver) &&
e.is_a?(Selenium::WebDriver::Error::StaleElementReferenceError)
# ignore and spin around for another check
false
else
raise
end
end
end
end
6 changes: 6 additions & 0 deletions lib/ae_page_objects/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class LoadingPageFailed < LoadingFailed
class LoadingElementFailed < LoadingFailed
end

class ElementNotPresent < Error
end

class ElementNotAbsent < Error
end

class PathNotResolvable < Error
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,17 @@ def test_element_proxy
assert author.rating.star.visible?
assert_false author.rating.star.not_present?
assert_false author.rating.star.not_visible?
end

assert_nothing_raised do
author.rating.star.wait_for_presence(0)
end

assert_raises AePageObjects::ElementNotAbsent do
author.rating.star.wait_for_absence(1)
end

Capybara.using_wait_time(1) do
author.rating.hide_star
assert author.rating.star.present?
assert_false author.rating.star.visible?
Expand All @@ -155,10 +165,35 @@ def test_element_proxy
end
end

def test_element_proxy__present_then_absent
author = PageObjects::Authors::NewPage.visit

author.rating.show_star

star = author.rating.star
assert star.present?

author.rating.remove_star

# use new object
assert author.rating.star.not_present?

# use existing object
assert star.not_present?
end

def test_element_proxy__not_present
author = PageObjects::Authors::NewPage.visit
assert_false author.missing.present?
assert author.missing.not_present?

assert_nothing_raised do
author.missing.wait_for_absence(0)
end

assert_raises AePageObjects::ElementNotPresent do
author.missing.wait_for_presence(2)
end
end

def test_element_proxy__nested
Expand Down
119 changes: 107 additions & 12 deletions test/unit/element_proxy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def test_initialize__no_block
assert_is_proxy proxy

element_class.expects(:new).returns(:yay)
assert_equal :yay, proxy.send(:element)
assert_equal :yay, proxy.send(:implicit_element)
end

def test_methods_forwarded
Expand Down Expand Up @@ -52,8 +52,10 @@ def test_present
def test_present__element_not_found
proxy = new_proxy

element_class.expects(:new).raises(AePageObjects::LoadingElementFailed)
assert_false proxy.present?
with_stubbed_wait_for do
element_class.expects(:new).raises(AePageObjects::LoadingElementFailed)
assert_false proxy.present?
end
end

def test_not_present
Expand Down Expand Up @@ -127,26 +129,119 @@ def test_visible__false
end
end

def test_wait_for_presence
proxy = new_proxy

element_class.expect_initialize
assert_nothing_raised do
proxy.wait_for_presence
end
end

def test_wait_for_presence__with_timeout
proxy = new_proxy

element_class.expect_initialize
assert_nothing_raised do
with_stubbed_wait_for(20) do
proxy.wait_for_presence(20)
end
end
end

def test_wait_for_presence__not_present
proxy = new_proxy

element_class.expects(:new).raises(AePageObjects::LoadingElementFailed)

raised = nil

with_stubbed_wait_for do
raised = assert_raise ElementNotPresent do
proxy.wait_for_presence
end
end

assert_includes raised.message, element_class.to_s
end

def test_wait_for_absence
proxy = new_proxy

element_class.expects(:new).raises(AePageObjects::LoadingElementFailed)
assert_nothing_raised do
proxy.wait_for_absence
end
end

def test_wait_for_absence__with_timeout
proxy = new_proxy

element_class.expects(:new).raises(AePageObjects::LoadingElementFailed)
assert_nothing_raised do
with_stubbed_wait_for(20) do
proxy.wait_for_absence(20)
end
end
end

def test_wait_for_absence__present
proxy = new_proxy

raised = assert_raise ElementNotAbsent do
with_stubbed_wait_for do
element_class.expect_initialize
proxy.wait_for_absence
end
end

assert_includes raised.message, element_class.to_s
end

def test_wait_for_absence__unknown
proxy = new_proxy

element_class.expects(:new).raises(Selenium::WebDriver::Error::StaleElementReferenceError)
capybara_stub.driver.expects(:is_a?).with(Capybara::Selenium::Driver).returns(true)

raised = assert_raise ElementNotAbsent do
with_stubbed_wait_for do
proxy.wait_for_absence
end
end

assert_includes raised.message, element_class.to_s
end

private

def unstub_wait_for
(class << Waiter; self; end).send(:alias_method, :wait_for, :wait_for_whatever)
waiter_singleton_class.class_eval do
alias_method :wait_for, :wait_for_whatever
undef_method :wait_for_whatever
end
end

(class << Waiter; self; end).send(:undef_method, :wait_for_whatever)
def waiter_singleton_class
(class << Waiter; self; end)
end

def stub_wait_for
wait_for_mock = mock(:wait_for_called => true)
(class << Waiter; self; end).send(:alias_method, :wait_for_whatever, :wait_for)
def stub_wait_for(expected_timeout = nil)
wait_for_mock = mock
wait_for_mock.expects(:wait_for_called).with(expected_timeout)

waiter_singleton_class.class_eval do
alias_method :wait_for_whatever, :wait_for
end

(class << Waiter; self; end).send(:define_method, :wait_for) do |&block|
wait_for_mock.wait_for_called
waiter_singleton_class.send(:define_method, :wait_for) do |*timeout, &block|
wait_for_mock.wait_for_called(*timeout)
block.call
end
end

def with_stubbed_wait_for
stub_wait_for
def with_stubbed_wait_for(expected_timeout = nil)
stub_wait_for(expected_timeout)
yield
ensure
unstub_wait_for
Expand Down

0 comments on commit 941181d

Please sign in to comment.