485 lines
18 KiB
Ruby
485 lines
18 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module QA
|
|
module Page
|
|
module MergeRequest
|
|
class Show < Page::Base
|
|
include Page::Component::Note
|
|
include Page::Component::Issuable::Sidebar
|
|
include Page::Component::RichTextPopover
|
|
|
|
view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
|
|
element :review_preview_dropdown
|
|
end
|
|
|
|
view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do
|
|
element :review_bar_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/batch_comments/components/submit_dropdown.vue' do
|
|
element :submit_review_dropdown
|
|
element :submit_review_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/diffs/components/compare_dropdown_layout.vue' do
|
|
element :dropdown_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/diffs/components/compare_versions.vue' do
|
|
element :target_version_dropdown
|
|
element :file_tree_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/diffs/components/tree_list.vue' do
|
|
element :file_tree_container
|
|
element :diff_tree_search
|
|
end
|
|
|
|
view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
|
|
element :file_name_content
|
|
element :file_title_container
|
|
element :dropdown_button
|
|
element :edit_in_ide_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/diffs/components/diff_row.vue' do
|
|
element :diff_comment_button
|
|
element :new_diff_line_link
|
|
end
|
|
|
|
view 'app/assets/javascripts/notes/components/note_form.vue' do
|
|
element :start_review_button
|
|
element :comment_now_button
|
|
end
|
|
|
|
view 'app/views/projects/merge_requests/_code_dropdown.html.haml' do
|
|
element 'mr-code-dropdown'
|
|
element 'download-email-patches-menu-item'
|
|
element 'download-plain-diff-menu-item'
|
|
element 'open-in-web-ide-button'
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue' do
|
|
element :merge_request_pipeline_info_content
|
|
element :pipeline_link
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue' do
|
|
element :merge_request_error_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
|
|
element :cherry_pick_button
|
|
element :revert_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue' do
|
|
element :mr_rebase_button
|
|
element :no_fast_forward_message_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
|
|
element :merge_button
|
|
element :merge_moment_dropdown
|
|
element :merge_immediately_menu_item
|
|
element :merged_status_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue' do
|
|
element :head_mismatch_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue' do
|
|
element :squash_checkbox
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue' do
|
|
element :mr_widget_content
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue' do
|
|
element :apply_suggestion_dropdown
|
|
element :commit_message_field
|
|
element :commit_with_custom_message_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_shared/components/markdown/header.vue' do
|
|
element :suggestion_button
|
|
element :dismiss_suggestion_popover_button
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue' do
|
|
element :add_suggestion_batch_button
|
|
element :applied_badge
|
|
element :applying_badge
|
|
end
|
|
|
|
view 'app/views/projects/merge_requests/_description.html.haml' do
|
|
element :description_content
|
|
end
|
|
|
|
view 'app/views/projects/merge_requests/_mr_title.html.haml' do
|
|
element :edit_title_button
|
|
element :title_content, required: true
|
|
end
|
|
|
|
view 'app/views/projects/merge_requests/_page.html.haml' do
|
|
element 'notes-tab', required: true
|
|
element 'commits-tab', required: true
|
|
element 'diffs-tab', required: true
|
|
end
|
|
|
|
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue' do
|
|
element :cancel_auto_merge_button
|
|
end
|
|
|
|
view 'app/views/shared/_broadcast_message.html.haml' do
|
|
element :broadcast_notification_container
|
|
element :close_button
|
|
end
|
|
|
|
def start_review
|
|
click_element(:start_review_button)
|
|
|
|
# After clicking the button, wait for it to disappear
|
|
# before moving on to the next part of the test
|
|
has_no_element?(:start_review_button)
|
|
end
|
|
|
|
def click_target_version_dropdown
|
|
click_element(:target_version_dropdown)
|
|
end
|
|
|
|
def version_dropdown_content
|
|
find_element(:dropdown_content).text
|
|
end
|
|
|
|
def submit_pending_reviews
|
|
# On test environments we have a broadcast message that can cover the buttons
|
|
|
|
if has_element?(:broadcast_notification_container, wait: 5)
|
|
within_element(:broadcast_notification_container) do
|
|
click_element(:close_button)
|
|
end
|
|
end
|
|
|
|
within_element(:review_bar_content) do
|
|
click_element(:review_preview_dropdown)
|
|
end
|
|
|
|
click_element(:submit_review_dropdown)
|
|
click_element(:submit_review_button)
|
|
|
|
# After clicking the button, wait for the review bar to disappear
|
|
# before moving on to the next part of the test
|
|
wait_until(reload: false) do
|
|
has_no_element?(:review_bar_content)
|
|
end
|
|
end
|
|
|
|
def add_comment_to_diff(text)
|
|
wait_until(sleep_interval: 5) do
|
|
has_css?('a[data-linenumber="1"]')
|
|
end
|
|
|
|
all_elements(:new_diff_line_link, minimum: 1).first.hover
|
|
click_element(:diff_comment_button)
|
|
click_element(:dismiss_suggestion_popover_button) if has_element?(:dismiss_suggestion_popover_button, wait: 1)
|
|
|
|
fill_element(:reply_field, text)
|
|
end
|
|
|
|
def click_discussions_tab
|
|
click_element('notes-tab')
|
|
|
|
wait_for_requests
|
|
end
|
|
|
|
def click_commits_tab
|
|
click_element('commits-tab')
|
|
end
|
|
|
|
def click_diffs_tab
|
|
# Do not wait for spinner due to https://gitlab.com/gitlab-org/gitlab/-/issues/398584
|
|
click_element('diffs-tab', skip_finished_loading_check: true)
|
|
end
|
|
|
|
def click_pipeline_link
|
|
click_element(:pipeline_link)
|
|
end
|
|
|
|
def edit!
|
|
# Click by JS is needed to bypass the Moved MR actions popover
|
|
# Change back to regular click_element when moved_mr_sidebar FF is removed
|
|
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
|
|
click_by_javascript(find_element(:edit_title_button, skip_finished_loading_check: true))
|
|
end
|
|
|
|
def fast_forward_not_possible?
|
|
has_element?(:no_fast_forward_message_content)
|
|
end
|
|
|
|
def has_file?(file_name)
|
|
open_file_tree
|
|
|
|
return true if has_element?(:file_name_content, file_name: file_name)
|
|
|
|
# Since the file tree uses virtual scrolling, search for file in case it is outside of viewport
|
|
search_file_tree(file_name)
|
|
has_element?(:file_name_content, file_name: file_name)
|
|
end
|
|
|
|
def has_no_file?(file_name)
|
|
# Since the file tree uses virtual scrolling, search for file to ensure non-existence
|
|
search_file_tree(file_name)
|
|
has_no_element?(:file_name_content, file_name: file_name)
|
|
end
|
|
|
|
def search_file_tree(file_name)
|
|
open_file_tree
|
|
fill_element(:diff_tree_search, file_name)
|
|
end
|
|
|
|
def open_file_tree
|
|
click_element(:file_tree_button) if has_no_element?(:file_tree_container, wait: 1)
|
|
end
|
|
|
|
def has_merge_button?
|
|
refresh
|
|
|
|
has_element?(:merge_button)
|
|
end
|
|
|
|
def has_no_merge_button?
|
|
refresh
|
|
|
|
has_no_element?(:merge_button)
|
|
end
|
|
|
|
RSpec::Matchers.define :have_merge_button do
|
|
match(&:has_merge_button?)
|
|
match_when_negated(&:has_no_merge_button?)
|
|
end
|
|
|
|
def has_pipeline_status?(text)
|
|
# Pipelines can be slow, so we wait a bit longer than the usual 10 seconds
|
|
wait_until(max_duration: 120, sleep_interval: 5, reload: true) do
|
|
has_element?(:merge_request_pipeline_info_content, text: text, wait: 15)
|
|
end
|
|
end
|
|
|
|
def has_title?(title)
|
|
has_element?(:title_content, text: title)
|
|
end
|
|
|
|
def has_description?(description)
|
|
has_element?(:description_content, text: description)
|
|
end
|
|
|
|
def mark_to_squash
|
|
# The squash checkbox is enabled via JS
|
|
wait_until(reload: false) do
|
|
!find_element(:squash_checkbox, visible: false).disabled?
|
|
end
|
|
|
|
check_element(:squash_checkbox, true)
|
|
end
|
|
|
|
def merge!
|
|
try_to_merge!
|
|
finished_loading?
|
|
|
|
raise "Merge did not appear to be successful" unless merged?
|
|
end
|
|
|
|
def merge_when_pipeline_succeeds!
|
|
wait_until_ready_to_merge
|
|
|
|
click_element(:merge_button, text: 'Merge when pipeline succeeds')
|
|
end
|
|
|
|
def merged?
|
|
# Reloads the page at this point to avoid the problem of the merge status failing to update
|
|
# That's the transient UX issue this test is checking for, so if the MR is merged but the UI still shows the
|
|
# status as unmerged, the test will fail.
|
|
# Revisit after merge page re-architect is done https://gitlab.com/groups/gitlab-org/-/epics/5598
|
|
# To remove page refresh logic if possible
|
|
# We don't raise on failure because this method is used as a predicate matcher
|
|
retry_until(max_attempts: 3, reload: true, raise_on_failure: false) do
|
|
has_element?(:merged_status_content, text: /The changes were merged into|Changes merged into/, wait: 20)
|
|
end
|
|
end
|
|
|
|
RSpec::Matchers.define :be_mergeable do
|
|
match do |page|
|
|
page.has_element?(:merge_button, disabled: false)
|
|
end
|
|
|
|
match_when_negated do |page|
|
|
page.has_no_element?(:merge_button, disabled: false)
|
|
end
|
|
end
|
|
|
|
# Waits up 10 seconds and returns false if the Revert button is not enabled
|
|
def revertible?
|
|
has_element?(:revert_button, disabled: false, wait: 10)
|
|
end
|
|
|
|
# Waits up 60 seconds and raises an error if unable to merge.
|
|
#
|
|
# If a state is encountered in which a user would typically refresh the page, this will refresh the page and
|
|
# then check again if it's ready to merge. For example, it will refresh if a new change was pushed and the page
|
|
# needs to be refreshed to show the change.
|
|
#
|
|
# @param [Boolean] transient_test true if the current test is a transient test (default: false)
|
|
def wait_until_ready_to_merge(transient_test: false)
|
|
wait_until(message: "Waiting for ready to merge", sleep_interval: 1) do
|
|
# changes in mr are rendered async, because of that mr can sometimes show no changes and there will be no
|
|
# merge button, in such case we must retry loop otherwise find_element will raise ElementNotFound error
|
|
next false unless has_element?(:merge_button, wait: 1)
|
|
|
|
break true unless find_element(:merge_button).disabled?
|
|
|
|
# If the widget shows "Merge blocked: new changes were just added" we can refresh the page and check again
|
|
next false if has_element?(:head_mismatch_content, wait: 1)
|
|
|
|
# Stop waiting if we're in a transient test. By this point we're in an unexpected state and should let the
|
|
# test fail so we can investigate. If we're not in a transient test we keep trying until we reach timeout.
|
|
next true unless transient_test
|
|
|
|
QA::Runtime::Logger.debug("MR widget text: #{mr_widget_text}")
|
|
|
|
false
|
|
end
|
|
end
|
|
|
|
def rebase!
|
|
# The rebase button is disabled on load
|
|
wait_until do
|
|
has_element?(:mr_rebase_button)
|
|
end
|
|
|
|
# The rebase button is enabled via JS
|
|
wait_until(reload: false) do
|
|
!find_element(:mr_rebase_button).disabled?
|
|
end
|
|
|
|
click_element(:mr_rebase_button)
|
|
end
|
|
|
|
def merge_immediately!
|
|
close_rich_text_promo_popover_if_present
|
|
retry_until(reload: true, sleep_interval: 1, max_attempts: 12) do
|
|
if has_element?(:merge_moment_dropdown)
|
|
click_element(:merge_moment_dropdown, skip_finished_loading_check: true)
|
|
click_element(:merge_immediately_menu_item, skip_finished_loading_check: true)
|
|
else
|
|
click_element(:merge_button, skip_finished_loading_check: true)
|
|
end
|
|
|
|
merged?
|
|
end
|
|
end
|
|
|
|
def try_to_merge!
|
|
close_rich_text_promo_popover_if_present
|
|
# Revisit after merge page re-architect is done https://gitlab.com/gitlab-org/gitlab/-/issues/300042
|
|
# To remove page refresh logic if possible
|
|
wait_until_ready_to_merge
|
|
wait_until { !find_element(:merge_button).text.include?('when pipeline succeeds') } # rubocop:disable Rails/NegateInclude
|
|
|
|
click_element(:merge_button)
|
|
end
|
|
|
|
def view_email_patches
|
|
# Click by JS is needed to bypass the Moved MR actions popover
|
|
# Change back to regular click_element when moved_mr_sidebar FF is removed
|
|
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
|
|
click_by_javascript(find_element('mr-code-dropdown'))
|
|
visit_link_in_element('download-email-patches-menu-item')
|
|
end
|
|
|
|
def view_plain_diff
|
|
# Click by JS is needed to bypass the Moved MR actions popover
|
|
# Change back to regular click_element when moved_mr_sidebar FF is removed
|
|
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
|
|
click_by_javascript(find_element('mr-code-dropdown'))
|
|
visit_link_in_element('download-plain-diff-menu-item')
|
|
end
|
|
|
|
def wait_for_merge_request_error_message
|
|
wait_until(max_duration: 30, reload: false) do
|
|
has_element?(:merge_request_error_content)
|
|
end
|
|
end
|
|
|
|
def click_open_in_web_ide
|
|
# Click by JS is needed to bypass the Moved MR actions popover
|
|
# Change back to regular click_element when moved_mr_sidebar FF is removed
|
|
# Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460
|
|
click_by_javascript(find_element('mr-code-dropdown'))
|
|
click_element('open-in-web-ide-button')
|
|
page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
|
|
wait_for_requests
|
|
end
|
|
|
|
def edit_file_in_web_ide(file_name)
|
|
within_element(:file_title_container, file_name: file_name) do
|
|
click_element(:dropdown_button)
|
|
click_element(:edit_in_ide_button)
|
|
end
|
|
page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
|
|
end
|
|
|
|
def add_suggestion_to_diff(suggestion, line)
|
|
find("a[data-linenumber='#{line}']").hover
|
|
click_element(:diff_comment_button)
|
|
click_element(:suggestion_button)
|
|
initial_content = find_element(:reply_field).value
|
|
fill_element(:reply_field, '')
|
|
fill_element(:reply_field, initial_content.gsub(/(```suggestion:-0\+0\n).*(\n```)/, "\\1#{suggestion}\\2"))
|
|
click_element(:comment_now_button)
|
|
wait_for_requests
|
|
end
|
|
|
|
def apply_suggestion_with_message(message)
|
|
all_elements(:apply_suggestion_dropdown, minimum: 1).first.click
|
|
fill_element(:commit_message_field, message)
|
|
click_element(:commit_with_custom_message_button)
|
|
end
|
|
|
|
def add_suggestion_to_batch
|
|
all_elements(:add_suggestion_batch_button, minimum: 1).first.click
|
|
end
|
|
|
|
def has_suggestions_applied?(count = 1)
|
|
wait_until(reload: false) do
|
|
has_no_element?(:applying_badge)
|
|
end
|
|
all_elements(:applied_badge, count: count)
|
|
end
|
|
|
|
def cherry_pick!
|
|
click_element(:cherry_pick_button, Page::Component::CommitModal)
|
|
click_element(:submit_commit_button)
|
|
end
|
|
|
|
def revert_change!
|
|
# reload page when the revert modal occasionally doesn't appear in ee:large-setup job
|
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/386623 (transient issue)
|
|
retry_on_exception(reload: true) do
|
|
click_element(:revert_button, Page::Component::CommitModal)
|
|
end
|
|
click_element(:submit_commit_button)
|
|
end
|
|
|
|
def mr_widget_text
|
|
find_element(:mr_widget_content).text
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
QA::Page::MergeRequest::Show.prepend_mod_with('Page::MergeRequest::Show', namespace: QA)
|