common_actions.rb 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
  2. module CommonActions
  3. delegate :app_host, to: Capybara
  4. # Performs a login with the given credentials and closes the clues (if present).
  5. # The 'remember me' can optionally be checked.
  6. #
  7. # @example
  8. # login(
  9. # username: 'admin@example.com',
  10. # password: 'test',
  11. # )
  12. #
  13. # @example
  14. # login(
  15. # username: 'admin@example.com',
  16. # password: 'test',
  17. # remember_me: true,
  18. # )
  19. #
  20. # return [nil]
  21. def login(username:, password:, remember_me: false)
  22. visit '/'
  23. within('#login') do
  24. fill_in 'username', with: username
  25. fill_in 'password', with: password
  26. # check via label because checkbox is hidden
  27. click('.checkbox-replacement') if remember_me
  28. # submit
  29. click_button
  30. end
  31. wait(4).until_exists do
  32. current_login
  33. end
  34. await_empty_ajax_queue
  35. end
  36. # Checks if the current session is logged in.
  37. #
  38. # @example
  39. # logged_in?
  40. # => true
  41. #
  42. # @return [true, false]
  43. def logged_in?
  44. current_login.present?
  45. rescue Capybara::ElementNotFound
  46. false
  47. end
  48. # Returns the login of the currently logged in user.
  49. #
  50. # @example
  51. # current_login
  52. # => 'admin@example.com'
  53. #
  54. # @return [String] the login of the currently logged in user.
  55. def current_login
  56. find('.user-menu .user a')[:title]
  57. end
  58. # Returns the User record for the currently logged in user.
  59. #
  60. # @example
  61. # current_user.login
  62. # => 'admin@example.com'
  63. #
  64. # @example
  65. # current_user do |user|
  66. # user.group_names_access_map = group_names_access_map
  67. # user.save!
  68. # end
  69. #
  70. # @return [User] the current user record.
  71. def current_user
  72. ::User.find_by(login: current_login).tap do |user|
  73. yield user if block_given?
  74. end
  75. end
  76. # Logs out the currently logged in user.
  77. #
  78. # @example
  79. # logout
  80. #
  81. def logout
  82. visit('logout')
  83. end
  84. # Overwrites the Capybara::Session#visit method to allow SPA navigation
  85. # and visiting of external URLs.
  86. # All routes not starting with `/` will be handled as SPA routes.
  87. # All routes containing `://` will be handled as an external URL.
  88. #
  89. # @see Capybara::Session#visit
  90. #
  91. # @example
  92. # visit('logout')
  93. # => visited SPA route 'localhost:32435/#logout'
  94. #
  95. # @example
  96. # visit('/test/ui')
  97. # => visited regular route 'localhost:32435/test/ui'
  98. #
  99. # @example
  100. # visit('https://zammad.org')
  101. # => visited external URL 'https://zammad.org'
  102. #
  103. def visit(route)
  104. if route.include?('://')
  105. return without_port do
  106. super(route)
  107. end
  108. elsif !route.start_with?('/')
  109. route = "/##{route}"
  110. end
  111. super(route)
  112. # wait for AJAX requets only on WebApp visits
  113. return if !route.start_with?('/#')
  114. return if route == '/#logout'
  115. # make sure all AJAX requests are done
  116. await_empty_ajax_queue
  117. # make sure loading is completed (e.g. ticket zoom may take longer)
  118. expect(page).to have_no_css('.icon-loading', wait: 30)
  119. end
  120. # Overwrites the global Capybara.always_include_port setting (true)
  121. # with false. This comes in handy when visiting external pages.
  122. #
  123. def without_port
  124. original = Capybara.current_session.config.always_include_port
  125. Capybara.current_session.config.always_include_port = false
  126. yield
  127. ensure
  128. Capybara.current_session.config.always_include_port = original
  129. end
  130. # This method is equivalent to Capybara::RSpecMatchers#have_current_path
  131. # but checks the SPA route instead of the actual path.
  132. #
  133. # @see Capybara::RSpecMatchers#have_current_path
  134. #
  135. # @example
  136. # expect(page).to have_current_route('login')
  137. # => checks for SPA route '/#login'
  138. #
  139. def have_current_route(route, **options)
  140. if route.is_a?(String)
  141. route = Regexp.new(Regexp.quote("/##{route}"))
  142. end
  143. # wait 1 sec by default because Firefox is slow
  144. options.reverse_merge!(wait: 1, url: true)
  145. have_current_path(route, **options)
  146. end
  147. # This is a convenient wrapper method around #have_current_route
  148. # which requires no previous `expect(page).to ` call.
  149. #
  150. # @example
  151. # expect_current_route('login')
  152. # => checks for SPA route '/#login'
  153. #
  154. def expect_current_route(route, **options)
  155. expect(page).to have_current_route(route, **options)
  156. end
  157. # Create and migrate an object manager attribute and verify that it exists. Returns the newly attribute.
  158. #
  159. # Create a select attribute:
  160. # @example
  161. # attribute = setup_attribute :object_manager_attribute_select
  162. #
  163. # Create a required text attribute:
  164. # @example
  165. # attribute = setup_attribute :object_manager_attribute_text,
  166. # screens: attributes_for(:required_screen)
  167. #
  168. # Create a date attribute with custom parameters:
  169. # @example
  170. # attribute = setup_attribute :object_manager_attribute_date,
  171. # data_option: {
  172. # 'future' => true,
  173. # 'past' => false,
  174. # 'diff' => 24,
  175. # 'null' => true,
  176. # }
  177. #
  178. # return [attribute]
  179. def create_attribute(attribute_name, attribute_parameters = {})
  180. attribute = create(attribute_name, attribute_parameters)
  181. ObjectManager::Attribute.migration_execute
  182. page.driver.browser.navigate.refresh
  183. attribute
  184. end
  185. # opens the macro list in the ticket view via click
  186. #
  187. # @example
  188. # open_macro_list
  189. #
  190. def open_macro_list
  191. click '.js-openDropdownMacro'
  192. end
  193. def open_article_meta
  194. retry_on_stale do
  195. wrapper = all('div.ticket-article-item').last
  196. wrapper.find('.article-content .textBubble').click
  197. wait(3).until do
  198. wrapper.find('.article-content-meta .article-meta.top').in_fixed_position
  199. end
  200. end
  201. end
  202. def use_template(template)
  203. wait(4).until do
  204. field = find('#form-template select[name="id"]')
  205. option = field.find(:option, template.name)
  206. option.select_option
  207. click '.sidebar-content .js-apply'
  208. # this is a workaround for a race condition where
  209. # the template selection get's re-rendered after
  210. # a selection was made. The selection is lost and
  211. # the apply click has no effect.
  212. template.options.any? do |attribute, value|
  213. selector = %([name="#{attribute}"])
  214. next if !page.has_css?(selector, wait: 0)
  215. find(selector, wait: 0, visible: false).value == value
  216. end
  217. end
  218. end
  219. # Checks if modal is ready
  220. #
  221. # @param timeout [Integer] seconds to wait
  222. def modal_ready(timeout: 4)
  223. wait(timeout).until_exists { find('.modal.in', wait: 0) }
  224. end
  225. # Checks if modal has disappeared
  226. #
  227. # @param timeout [Integer] seconds to wait
  228. def modal_disappear(timeout: 4)
  229. wait(timeout).until_disappears { find('.modal', wait: 0) }
  230. end
  231. # Executes action inside of modal. Makes sure modal has opened and closes
  232. #
  233. # @param timeout [Integer] seconds to wait
  234. # @param wait_for_disappear [Bool] wait for modal to close
  235. def in_modal(timeout: 4, disappears: true, &block)
  236. modal_ready(timeout: timeout)
  237. within('.modal', &block)
  238. modal_disappear(timeout: timeout) if disappears
  239. end
  240. # Show the popover on hover
  241. #
  242. # @example
  243. # popover_on_hover(page.find('button.hover_me'))
  244. #
  245. def popover_on_hover(element, wait_for_popover_killer: true)
  246. # wait for popover killer to pass
  247. sleep 3 if wait_for_popover_killer
  248. move_mouse_to(element)
  249. move_mouse_by(5, 5)
  250. end
  251. # Scroll into view with javscript.
  252. #
  253. # @param position [Symbol] :top or :bottom, position of the scroll into view
  254. #
  255. # scroll_into_view('button.js-submit)
  256. #
  257. def scroll_into_view(css_selector, position: :top)
  258. page.execute_script("document.querySelector('#{css_selector}').scrollIntoView(#{position == :top})")
  259. sleep 0.3
  260. end
  261. end
  262. RSpec.configure do |config|
  263. config.include CommonActions, type: :system
  264. end