2022-01-01 13:38:12 +00:00
|
|
|
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
2021-06-01 12:20:20 +00:00
|
|
|
|
2018-12-19 14:47:15 +00:00
|
|
|
module BrowserTestHelper
|
|
|
|
|
2020-09-23 14:42:31 +00:00
|
|
|
# Sometimes tests refer to elements that get removed/re-added to the DOM when
|
|
|
|
# updating the UI. This causes Selenium to throw a StaleElementReferenceError exception.
|
|
|
|
# This method catches this error and retries the given amount of times re-raising
|
|
|
|
# the exception if the element is still stale.
|
|
|
|
# @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/StaleElementReference WebDriver definition
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# retry_on_stale do
|
|
|
|
# find('.now-here-soon-gone').click
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# retry_on_stale(retries: 10) do
|
|
|
|
# find('.now-here-soon-gone').click
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# @raise [Selenium::WebDriver::Error::StaleElementReferenceError] If element is still stale after given number of retries
|
|
|
|
def retry_on_stale(retries: 3)
|
|
|
|
tries ||= 0
|
|
|
|
|
|
|
|
yield
|
|
|
|
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
|
|
|
raise if tries == retries
|
|
|
|
|
|
|
|
wait_time = tries
|
|
|
|
tries += 1
|
|
|
|
|
|
|
|
Rails.logger.info "Stale element found. Retry #{tries}/retries (sleeping: #{wait_time})"
|
|
|
|
sleep wait_time
|
|
|
|
end
|
|
|
|
|
2021-10-12 14:02:34 +00:00
|
|
|
# Get the current cookies from the browser with the driver object.
|
|
|
|
#
|
|
|
|
def cookies
|
|
|
|
page.driver.browser.manage.all_cookies
|
|
|
|
end
|
|
|
|
|
|
|
|
# Get a single cookie by the given name (regex possible)
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# cookie('cookie-name')
|
|
|
|
#
|
|
|
|
def cookie(name)
|
|
|
|
cookies.find { |cookie| cookie[:name].match?(name) }
|
|
|
|
end
|
|
|
|
|
2018-12-19 14:47:15 +00:00
|
|
|
# Finds an element and clicks it - wrapped in one method.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# click('.js-channel .btn.email')
|
|
|
|
#
|
|
|
|
# click(:href, '#settings/branding')
|
|
|
|
#
|
|
|
|
def click(*args)
|
|
|
|
find(*args).click
|
|
|
|
end
|
|
|
|
|
|
|
|
# This is a wrapper around the Selenium::WebDriver::Wait class
|
|
|
|
# with additional methods.
|
|
|
|
# @see BrowserTestHelper::Waiter
|
|
|
|
#
|
|
|
|
# @example
|
2021-12-10 12:37:47 +00:00
|
|
|
# wait.until { ... }
|
2018-12-19 14:47:15 +00:00
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# wait(5, interval: 0.5).until { ... }
|
|
|
|
#
|
|
|
|
def wait(seconds = Capybara.default_max_wait_time, **kargs)
|
|
|
|
wait_args = Hash(kargs).merge(timeout: seconds)
|
|
|
|
wait_handle = Selenium::WebDriver::Wait.new(wait_args)
|
|
|
|
Waiter.new(wait_handle)
|
|
|
|
end
|
|
|
|
|
2020-06-02 11:01:16 +00:00
|
|
|
# This checks the number of queued AJAX requests in the frontend JS app
|
|
|
|
# and assures that the number is constantly zero for 0.5 seconds.
|
|
|
|
# It comes in handy when waiting for AJAX requests to be completed
|
|
|
|
# before performing further actions.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# await_empty_ajax_queue
|
|
|
|
#
|
|
|
|
def await_empty_ajax_queue
|
2021-08-16 08:37:14 +00:00
|
|
|
# page.evaluate_script silently discards any present alerts, which is not desired.
|
|
|
|
begin
|
|
|
|
return if page.driver.browser.switch_to.alert
|
|
|
|
rescue Selenium::WebDriver::Error::NoSuchAlertError # rubocop:disable Lint/SuppressedException
|
|
|
|
end
|
|
|
|
|
2021-06-15 06:26:52 +00:00
|
|
|
wait(5, interval: 0.1).until_constant do
|
2022-03-04 12:21:07 +00:00
|
|
|
page.evaluate_script('App.Ajax.queue().length === 0 && $.active === 0 && Object.keys(App.FormHandlerCoreWorkflow.getRequests()).length === 0').present?
|
2020-06-02 11:01:16 +00:00
|
|
|
end
|
2021-06-15 06:26:52 +00:00
|
|
|
rescue
|
|
|
|
nil
|
2020-06-02 11:01:16 +00:00
|
|
|
end
|
|
|
|
|
2019-09-04 07:42:50 +00:00
|
|
|
# Moves the mouse from its current position by the given offset.
|
|
|
|
# If the coordinates provided are outside the viewport (the mouse will end up outside the browser window)
|
|
|
|
# then the viewport is scrolled to match.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# move_mouse_by(x, y)
|
|
|
|
# move_mouse_by(100, 200)
|
|
|
|
#
|
|
|
|
def move_mouse_by(x_axis, y_axis)
|
|
|
|
page.driver.browser.action.move_by(x_axis, y_axis).perform
|
|
|
|
end
|
|
|
|
|
2020-12-08 09:17:21 +00:00
|
|
|
# Moves the mouse to element.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# move_mouse_to(page.find('button.hover_me'))
|
|
|
|
#
|
|
|
|
def move_mouse_to(element)
|
|
|
|
element.in_fixed_position
|
|
|
|
page.driver.browser.action.move_to_location(element.native.location.x, element.native.location.y).perform
|
|
|
|
end
|
|
|
|
|
2019-09-04 07:42:50 +00:00
|
|
|
# Clicks and hold (without releasing) in the middle of the given element.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# click_and_hold(ticket)
|
|
|
|
# click_and_hold(tr[data-id='1'])
|
|
|
|
#
|
|
|
|
def click_and_hold(element)
|
|
|
|
page.driver.browser.action.click_and_hold(element).perform
|
|
|
|
end
|
|
|
|
|
2021-07-01 09:03:41 +00:00
|
|
|
# Clicks and hold (without releasing) in the middle of the given element
|
|
|
|
# and moves it to the top left of the page to show marcos batches in
|
|
|
|
# overview section.
|
|
|
|
#
|
|
|
|
# @example
|
2021-12-12 15:45:33 +00:00
|
|
|
# display_macro_batches(Ticket.first)
|
2021-07-01 09:03:41 +00:00
|
|
|
#
|
2021-12-12 15:45:33 +00:00
|
|
|
def display_macro_batches(ticket)
|
|
|
|
# get DOM element
|
|
|
|
element = page.find(:table_row, ticket.id).native
|
|
|
|
# get element moving
|
2021-07-01 09:03:41 +00:00
|
|
|
click_and_hold(element)
|
|
|
|
# move element to y -ticket.location.y
|
|
|
|
move_mouse_by(0, -element.location.y + 5)
|
|
|
|
# move a bit to the left to display macro batches
|
|
|
|
move_mouse_by(-250, 0)
|
|
|
|
end
|
|
|
|
|
2019-09-04 07:42:50 +00:00
|
|
|
# Releases the depressed left mouse button at the current mouse location.
|
|
|
|
#
|
|
|
|
# @example
|
|
|
|
# release_mouse
|
|
|
|
#
|
|
|
|
def release_mouse
|
|
|
|
page.driver.browser.action.release.perform
|
2021-06-15 06:26:52 +00:00
|
|
|
await_empty_ajax_queue
|
2019-09-04 07:42:50 +00:00
|
|
|
end
|
|
|
|
|
2018-12-19 14:47:15 +00:00
|
|
|
class Waiter < SimpleDelegator
|
|
|
|
|
|
|
|
# This method is a derivation of Selenium::WebDriver::Wait#until
|
|
|
|
# which ignores Capybara::ElementNotFound exceptions raised
|
|
|
|
# in the given block.
|
|
|
|
#
|
|
|
|
# @example
|
2021-12-10 12:37:47 +00:00
|
|
|
# wait.until_exists { find('[data-title="example"]') }
|
2018-12-19 14:47:15 +00:00
|
|
|
#
|
|
|
|
def until_exists
|
|
|
|
self.until do
|
2019-06-27 18:26:28 +00:00
|
|
|
|
|
|
|
yield
|
2020-05-25 07:05:17 +00:00
|
|
|
rescue Capybara::ElementNotFound
|
2020-06-08 07:17:29 +00:00
|
|
|
# doesn't exist yet
|
2018-12-19 14:47:15 +00:00
|
|
|
end
|
2021-10-22 14:36:06 +00:00
|
|
|
rescue Selenium::WebDriver::Error::TimeoutError => e
|
2018-12-19 14:47:15 +00:00
|
|
|
# cleanup backtrace
|
|
|
|
e.set_backtrace(e.backtrace.drop(3))
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
|
2019-10-21 11:03:54 +00:00
|
|
|
# This method is a derivation of Selenium::WebDriver::Wait#until
|
|
|
|
# which ignores Capybara::ElementNotFound exceptions raised
|
|
|
|
# in the given block.
|
|
|
|
#
|
|
|
|
# @example
|
2021-12-10 12:37:47 +00:00
|
|
|
# wait.until_disappear { find('[data-title="example"]') }
|
2019-10-21 11:03:54 +00:00
|
|
|
#
|
|
|
|
def until_disappears
|
|
|
|
self.until do
|
|
|
|
|
|
|
|
yield
|
|
|
|
false
|
|
|
|
rescue Capybara::ElementNotFound
|
|
|
|
true
|
|
|
|
end
|
2021-10-22 14:36:06 +00:00
|
|
|
rescue Selenium::WebDriver::Error::TimeoutError => e
|
2019-10-21 11:03:54 +00:00
|
|
|
# cleanup backtrace
|
|
|
|
e.set_backtrace(e.backtrace.drop(3))
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
|
2018-12-19 14:47:15 +00:00
|
|
|
# This method loops a given block until the result of it is constant.
|
|
|
|
#
|
|
|
|
# @example
|
2021-12-10 12:37:47 +00:00
|
|
|
# wait.until_constant { find('.total').text }
|
2018-12-19 14:47:15 +00:00
|
|
|
#
|
|
|
|
def until_constant
|
|
|
|
previous = nil
|
2020-09-23 10:07:46 +00:00
|
|
|
timeout = __getobj__.instance_variable_get(:@timeout)
|
|
|
|
interval = __getobj__.instance_variable_get(:@interval)
|
|
|
|
rounds = (timeout / interval).to_i
|
|
|
|
|
|
|
|
rounds.times do
|
|
|
|
sleep interval
|
|
|
|
|
2018-12-19 14:47:15 +00:00
|
|
|
latest = yield
|
2020-09-23 10:07:46 +00:00
|
|
|
|
|
|
|
next if latest.nil?
|
2018-12-19 14:47:15 +00:00
|
|
|
break if latest == previous
|
|
|
|
|
|
|
|
previous = latest
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
RSpec.configure do |config|
|
|
|
|
config.include BrowserTestHelper, type: :system
|
|
|
|
end
|