# frozen_string_literal: true require "highline/import" module SystemHelpers def pause_test result = ask( "\n\e[33mTest paused, press enter to resume, type `d` and press enter to start debugger.\e[0m", ) binding.pry if result == "d" # rubocop:disable Lint/Debugger self end def sign_in(user) visit "/session/#{user.encoded_username}/become.json?redirect=false" end def sign_out delete "/session" end def setup_system_test SiteSetting.login_required = false SiteSetting.content_security_policy = false SiteSetting.force_hostname = Capybara.server_host SiteSetting.port = Capybara.server_port SiteSetting.external_system_avatars_enabled = false SiteSetting.disable_avatar_education_message = true SiteSetting.enable_user_tips = false end def try_until_success(timeout: 2, frequency: 0.01) start ||= Time.zone.now backoff ||= frequency yield rescue RSpec::Expectations::ExpectationNotMetError, Capybara::ExpectationNotMet, Capybara::ElementNotFound raise if Time.zone.now >= start + timeout.seconds sleep backoff backoff += frequency retry end def wait_for_attribute( element, attribute, value, timeout: Capybara.default_max_wait_time, frequency: 0.01 ) try_until_success(timeout: timeout, frequency: frequency) do expect(element[attribute.to_sym]).to eq(value) end end # Waits for an element to stop animating up to timeout seconds, # then raises a Capybara error if it does not stop. # # This is based on getBoundingClientRect, where Y is the distance # from the top of the element to the top of the viewport, and X # is the distance from the leftmost edge of the element to the # left of the viewport. The viewpoint origin (0, 0) is at the # top left of the page. # # Once X and Y stop changing based on the current vs previous position, # then we know the animation has stopped and the element is stabilised, # at which point we can click on it without fear of Capybara mis-clicking. # # c.f. https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect def wait_for_animation(element, timeout: Capybara.default_max_wait_time) old_element_x = nil old_element_y = nil try_until_success(timeout: timeout) do current_element_x = element.rect.x current_element_y = element.rect.y stopped_moving = current_element_x == old_element_x && current_element_y == old_element_y old_element_x = current_element_x old_element_y = current_element_y raise Capybara::ExpectationNotMet if !stopped_moving end end def resize_window(width: nil, height: nil) original_size = page.driver.browser.manage.window.size page.driver.browser.manage.window.resize_to( width || original_size.width, height || original_size.height, ) yield ensure page.driver.browser.manage.window.resize_to(original_size.width, original_size.height) end def using_browser_timezone(timezone, &example) previous_browser_timezone = ENV["TZ"] ENV["TZ"] = timezone using_session(timezone) do |session| freeze_time(&example) session.quit end ENV["TZ"] = previous_browser_timezone end # When using parallelism, Capybara's `using_session` method can cause # intermittent failures as two sessions can be created with the same name # in different tests and be run at the same time. def using_session(name, &block) Capybara.using_session(name.to_s + self.method_name, &block) end def select_text_range(selector, start = 0, offset = 5) js = <<-JS const node = document.querySelector(arguments[0]).childNodes[0]; const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(node); range.setStart(node, arguments[1]); range.setEnd(node, arguments[1] + arguments[2]); selection.removeAllRanges(); selection.addRange(range); JS page.execute_script(js, selector, start, offset) end end