diff options
Diffstat (limited to 'qa')
114 files changed, 1505 insertions, 350 deletions
diff --git a/qa/Gemfile b/qa/Gemfile index d946b22a0e0..f00f26a5482 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' gem 'gitlab-qa' -gem 'activesupport', '~> 6.0.3.1' # This should stay in sync with the root's Gemfile +gem 'activesupport', '~> 6.0.3.3' # This should stay in sync with the root's Gemfile gem 'capybara', '~> 3.29.0' gem 'capybara-screenshot', '~> 1.0.23' gem 'rake', '~> 12.3.3' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 2356c90a0af..6cdedc3834d 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: abstract_type (0.0.7) - activesupport (6.0.3.1) + activesupport (6.0.3.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -38,7 +38,7 @@ GEM concord (0.1.5) adamantium (~> 0.2.0) equalizer (~> 0.0.9) - concurrent-ruby (1.1.6) + concurrent-ruby (1.1.7) debase (0.2.4.1) debase-ruby_core_source (>= 0.10.2) debase-ruby_core_source (0.10.6) @@ -52,7 +52,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (1.8.2) + i18n (1.8.5) concurrent-ruby (~> 1.0) ice_nine (0.11.2) knapsack (1.17.1) @@ -67,11 +67,11 @@ GEM mime-types-data (3.2020.0425) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.14.2) netrc (0.11.0) nokogiri (1.10.9) mini_portile2 (~> 2.4.0) - parallel (1.17.0) + parallel (1.19.2) parallel_tests (2.29.0) parallel parser (2.7.1.4) @@ -145,13 +145,13 @@ GEM procto (~> 0.0.2) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.3.0) + zeitwerk (2.4.0) PLATFORMS ruby DEPENDENCIES - activesupport (~> 6.0.3.1) + activesupport (~> 6.0.3.3) airborne (~> 0.3.4) capybara (~> 3.29.0) capybara-screenshot (~> 1.0.23) @@ -91,6 +91,7 @@ module QA autoload :UserGPG, 'qa/resource/user_gpg' autoload :Visibility, 'qa/resource/visibility' autoload :ProjectSnippet, 'qa/resource/project_snippet' + autoload :Design, 'qa/resource/design' module KubernetesCluster autoload :Base, 'qa/resource/kubernetes_cluster/base' @@ -260,6 +261,7 @@ module QA module Pipeline autoload :Index, 'qa/page/project/pipeline/index' autoload :Show, 'qa/page/project/pipeline/show' + autoload :New, 'qa/page/project/pipeline/new' end module Tag @@ -371,6 +373,7 @@ module QA module Snippet autoload :New, 'qa/page/project/snippet/new' autoload :Show, 'qa/page/project/snippet/show' + autoload :Index, 'qa/page/project/snippet/index' end end diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 035946b8471..512653e8c4a 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -59,6 +59,10 @@ module QA self.username, self.password = default_credentials end + def use_default_identity + configure_identity('GitLab QA', 'root@gitlab.com') + end + def clone(opts = '') clone_result = run("git clone #{opts} #{uri} ./", max_attempts: 3) return clone_result.response unless clone_result.success? @@ -122,8 +126,12 @@ module QA run("git rev-parse --abbrev-ref HEAD").to_s end - def push_changes(branch = 'master') - run("git push #{uri} #{branch}", max_attempts: 3).to_s + def push_changes(branch = 'master', push_options: nil) + cmd = ['git push'] + cmd << push_options_hash_to_string(push_options) + cmd << uri + cmd << branch + run(cmd.compact.join(' '), max_attempts: 3).to_s end def push_all_branches @@ -205,6 +213,10 @@ module QA run("cat #{file}").to_s end + def delete_netrc + File.delete(netrc_file_path) if File.exist?(netrc_file_path) + end + private attr_reader :uri, :username, :password, :known_hosts_file, @@ -216,7 +228,7 @@ module QA alias_method :to_s, :response def success? - exitstatus == 0 + exitstatus == 0 && !response.include?('Error encountered') end end @@ -293,6 +305,23 @@ module QA @tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s) end + def push_options_hash_to_string(opts) + return if opts.nil? + + prefix = "-o merge_request" + opts.each_with_object([]) do |(key, value), options| + if value.is_a?(Array) + value.each do |item| + options << "#{prefix}.#{key}=\"#{item}\"" + end + elsif value == true + options << "#{prefix}.#{key}" + else + options << "#{prefix}.#{key}=\"#{value}\"" + end + end.join(' ') + end + def netrc_file_path @netrc_file_path ||= File.join(tmp_home_dir, '.netrc') end diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb index a8a24bd3949..44d6b02ccd8 100644 --- a/qa/qa/page/component/design_management.rb +++ b/qa/qa/page/component/design_management.rb @@ -31,6 +31,16 @@ module QA element :design_file_name element :design_image end + + view 'app/assets/javascripts/design_management/pages/index.vue' do + element :archive_button + element :design_checkbox + element :design_dropzone_content + end + + view 'app/assets/javascripts/design_management/components/delete_button.vue' do + element :confirm_archiving_button + end end end @@ -52,12 +62,14 @@ module QA # It accepts a `class:` option, but that only works for class attributes # It doesn't work as a CSS selector. # So instead we use the name attribute as a locator - page.attach_file("design_file", design_file_path, make_visible: { display: 'block' }) + within_element(:design_dropzone_content) do + page.attach_file("design_file", design_file_path, make_visible: { display: 'block' }) + end filename = ::File.basename(design_file_path) found = wait_until(reload: false, sleep_interval: 1) do - image = find_element(:design_image) + image = find_element(:design_image, filename: filename) has_element?(:design_file_name, text: filename) && image["complete"] && @@ -71,11 +83,24 @@ module QA click_element(:design_file_name, text: filename) end + def select_design(filename) + click_element(:design_checkbox, design: filename) + end + + def archive_selected_designs + click_element(:archive_button) + click_element(:confirm_archiving_button) + end + def has_annotation?(note) within_element_by_index(:design_discussion_content, 0) do has_element?(:note_content, text: note) end end + + def has_design?(filename) + has_element?(:design_file_name, text: filename) + end end end end diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb index 4e94049efe7..82347ee209a 100644 --- a/qa/qa/page/component/issuable/sidebar.rb +++ b/qa/qa/page/component/issuable/sidebar.rb @@ -18,16 +18,29 @@ module QA element :more_assignees_link end - base.view 'app/helpers/dropdowns_helper.rb' do + base.view 'app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue' do + element :labels_block + end + + base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue' do + element :selected_label_content + end + + base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue' do + element :labels_dropdown_content + end + + base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do + element :labels_edit_button + end + + base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue' do element :dropdown_input_field end base.view 'app/views/shared/issuable/_sidebar.html.haml' do element :assignee_block - element :dropdown_menu_labels - element :edit_labels_link element :edit_milestone_link - element :labels_block element :milestone_block element :milestone_link end @@ -64,7 +77,7 @@ module QA def has_label?(label) within_element(:labels_block) do - !!has_element?(:label, label_name: label) + !!has_element?(:selected_label_content, label_name: label) end end @@ -80,23 +93,25 @@ module QA def select_labels_and_refresh(labels) Support::Retrier.retry_until do - click_element(:edit_labels_link) - has_element?(:dropdown_menu_labels, text: labels.first) + click_element(:labels_edit_button) + has_element?(:labels_dropdown_content, text: labels.first) end labels.each do |label| - within_element(:dropdown_menu_labels, text: label) do + within_element(:labels_dropdown_content) do send_keys_to_element(:dropdown_input_field, [label, :enter]) end end - click_element(:edit_labels_link) + click_element(:labels_edit_button) labels.each do |label| has_element?(:labels_block, text: label, wait: 0) end refresh + + wait_for_requests end def toggle_more_assignees_link diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb index 3e5ae29177a..ff9ece9ad10 100644 --- a/qa/qa/page/component/new_snippet.rb +++ b/qa/qa/page/component/new_snippet.rb @@ -21,18 +21,11 @@ module QA base.view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do element :file_name_field + element :file_holder_container end - base.view 'app/views/shared/form_elements/_description.html.haml' do - element :issuable_form_description - end - - base.view 'app/views/shared/snippets/_form.html.haml' do - element :snippet_description_field - element :description_placeholder - element :snippet_title_field - element :file_name_field - element :submit_button + base.view 'app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue' do + element :add_file_button end base.view 'app/views/shared/_zen.html.haml' do @@ -54,12 +47,28 @@ module QA choose visibility end - def fill_file_name(name) - fill_element :file_name_field, name + def fill_file_name(name, file_number = nil) + if file_number + within_element_by_index(:file_holder_container, file_number - 1) do + fill_element(:file_name_field, name) + end + else + fill_element(:file_name_field, name) + end + end + + def fill_file_content(content, file_number = nil) + if file_number + within_element_by_index(:file_holder_container, file_number - 1) do + text_area.set(content) + end + else + text_area.set content + end end - def fill_file_content(content) - text_area.set content + def click_add_file + click_element(:add_file_button) end def click_create_snippet_button diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb index 0e9cdd49519..e6defd2ec0c 100644 --- a/qa/qa/page/component/note.rb +++ b/qa/qa/page/component/note.rb @@ -9,9 +9,18 @@ module QA def self.included(base) super + base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do + element :toggle_comments_button + end + + base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do + element :discussion_reply_tab + element :resolve_discussion_button + end + base.view 'app/assets/javascripts/notes/components/comment_form.vue' do element :note_dropdown - element :discussion_option + element :discussion_menu_item end base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do @@ -23,39 +32,32 @@ module QA end base.view 'app/assets/javascripts/notes/components/note_form.vue' do - element :reply_input + element :reply_field element :reply_comment_button end - base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do - element :discussion_reply_tab - element :resolve_discussion_button - end - base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do - element :expand_replies - element :collapse_replies + element :expand_replies_button + element :collapse_replies_button end - base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do - element :toggle_comments_button + base.view 'app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue' do + element :skeleton_note_placeholder end end - def start_discussion(text) - fill_element :comment_input, text - click_element :note_dropdown - click_element :discussion_option - click_element :comment_button + def collapse_replies + click_element :collapse_replies_button end - def toggle_comments(position) - all_elements(:toggle_comments_button, minimum: position)[position - 1].click + def edit_comment(text) + click_element :note_edit_button + fill_element :reply_field, text + click_element :reply_comment_button end - def type_reply_to_discussion(position, reply_text) - all_elements(:discussion_reply_tab, minimum: position)[position - 1].click - fill_element :reply_input, reply_text + def expand_replies + click_element :expand_replies_button end def reply_to_discussion(position, reply_text) @@ -69,18 +71,24 @@ module QA end end - def collapse_replies - click_element :collapse_replies + def start_discussion(text) + fill_element :comment_field, text + click_element :note_dropdown + click_element :discussion_menu_item + click_element :comment_button end - def expand_replies - click_element :expand_replies + def toggle_comments(position) + all_elements(:toggle_comments_button, minimum: position)[position - 1].click end - def edit_comment(text) - click_element :note_edit_button - fill_element :reply_input, text - click_element :reply_comment_button + def type_reply_to_discussion(position, reply_text) + all_elements(:discussion_reply_tab, minimum: position)[position - 1].click + fill_element :reply_field, reply_text + end + + def wait_for_loading + has_no_element?(:skeleton_note_placeholer) end end end diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index 761bbb17168..87aed0105aa 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -43,6 +43,8 @@ module QA end def wait_for_search_to_complete + Support::WaitForRequests.wait_for_requests + has_css?('.select2-active', wait: 1) has_no_css?('.select2-active', wait: 30) end diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index 2776b6c078e..7074d7e7649 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -98,15 +98,39 @@ module QA end end - def has_file_name?(file_name) - within_element(:file_title_content) do - has_text?(file_name) - end - end - - def has_file_content?(file_content) - within_element(:file_content) do - has_text?(file_content) + def has_file_name?(file_name, file_number = nil) + if file_number + within_element_by_index(:file_title_content, file_number - 1) do + has_text?(file_name) + end + else + within_element(:file_title_content) do + has_text?(file_name) + end + end + end + + def has_file_content?(file_content, file_number = nil) + if file_number + within_element_by_index(:file_content, file_number - 1) do + has_text?(file_content) + end + else + within_element(:file_content) do + has_text?(file_content) + end + end + end + + def has_no_file_content?(file_content, file_number = nil) + if file_number + within_element_by_index(:file_content, file_number - 1) do + has_no_text?(file_content) + end + else + within_element(:file_content) do + has_no_text?(file_content) + end end end diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb index 7802a680c25..31fc69a04cc 100644 --- a/qa/qa/page/dashboard/snippet/edit.rb +++ b/qa/qa/page/dashboard/snippet/edit.rb @@ -5,10 +5,6 @@ module QA module Dashboard module Snippet class Edit < Page::Base - view 'app/views/shared/snippets/_form.html.haml' do - element :submit_button - end - view 'app/assets/javascripts/snippets/components/edit.vue' do element :submit_button end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 0b80ba84fa4..164f25389c0 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -44,15 +44,23 @@ module QA element :squash_checkbox end - view 'app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue' do - element :skeleton_note - end - view 'app/views/projects/merge_requests/show.html.haml' do element :notes_tab element :diffs_tab 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 + end + + view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do + element :file_name_content + end + view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do element :new_diff_line end @@ -67,15 +75,13 @@ module QA view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do element :review_bar - element :discard_review - element :modal_delete_pending_comments end view 'app/assets/javascripts/notes/components/note_form.vue' do element :unresolve_review_discussion_checkbox element :resolve_review_discussion_checkbox - element :start_review - element :comment_now + element :start_review_button + element :comment_now_button end view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do @@ -83,46 +89,54 @@ module QA end def start_review - click_element :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 + has_no_element?(:start_review_button) + end + + def click_target_version_dropdown + click_element(:target_version_dropdown) end def comment_now - click_element :comment_now + click_element(:comment_now_button) # After clicking the button, wait for it to disappear # before moving on to the next part of the test - has_no_element? :comment_now + has_no_element?(:comment_now_button) + end + + def version_dropdown_content + find_element(:dropdown_content).text end def submit_pending_reviews - within_element :review_bar do - click_element :review_preview_toggle - click_element :submit_review + within_element(:review_bar) do + click_element(:review_preview_toggle) + click_element(:submit_review) # After clicking the button, wait for it to disappear # before moving on to the next part of the test - has_no_element? :submit_review + has_no_element?(:submit_review) end end def discard_pending_reviews - within_element :review_bar do - click_element :discard_review + within_element(:review_bar) do + click_element(:discard_review) end - click_element :modal_delete_pending_comments + click_element(:modal_delete_pending_comments) end def resolve_review_discussion - scroll_to_element :start_review - check_element :resolve_review_discussion_checkbox + scroll_to_element(:start_review_button) + check_element(:resolve_review_discussion_checkbox) end def unresolve_review_discussion - check_element :unresolve_review_discussion_checkbox + check_element(:unresolve_review_discussion_checkbox) end def add_comment_to_diff(text) @@ -131,7 +145,7 @@ module QA end all_elements(:new_diff_line, minimum: 1).first.hover click_element(:diff_comment) - fill_element(:reply_input, text) + fill_element(:reply_field, text) end def click_discussions_tab @@ -160,6 +174,10 @@ module QA has_no_text?('Fast-forward merge is not possible') end + def has_file?(file_name) + has_element?(:file_name_content, text: file_name) + end + def has_merge_button? refresh @@ -168,7 +186,7 @@ module QA def has_pipeline_status?(text) # Pipelines can be slow, so we wait a bit longer than the usual 10 seconds - has_element?(:merge_request_pipeline_info_content, text: text, wait: 30) + has_element?(:merge_request_pipeline_info_content, text: text, wait: 60) end def has_title?(title) @@ -190,7 +208,7 @@ module QA !find_element(:squash_checkbox).disabled? end - click_element :squash_checkbox + click_element(:squash_checkbox) end def merge! @@ -202,7 +220,7 @@ module QA end def merged? - has_element?(:merged_status_content, text: 'The changes were merged into', wait: 30) + has_element?(:merged_status_content, text: 'The changes were merged into', wait: 60) end # Check if the MR is able to be merged @@ -235,7 +253,7 @@ module QA !find_element(:mr_rebase_button).disabled? end - click_element :mr_rebase_button + click_element(:mr_rebase_button) success = wait_until do has_text?('Fast-forward merge without a merge commit') @@ -251,12 +269,12 @@ module QA end def view_email_patches - click_element :download_dropdown + click_element(:download_dropdown) visit_link_in_element(:download_email_patches) end def view_plain_diff - click_element :download_dropdown + click_element(:download_dropdown) visit_link_in_element(:download_plain_diff) end @@ -266,10 +284,6 @@ module QA end end - def wait_for_loading - has_no_element?(:skeleton_note) - end - def click_open_in_web_ide click_element(:open_in_web_ide_button) wait_for_requests diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb index 810877e21ad..d27e113832c 100644 --- a/qa/qa/page/profile/ssh_keys.rb +++ b/qa/qa/page/profile/ssh_keys.rb @@ -11,8 +11,9 @@ module QA element :add_key_button end - view 'app/views/profiles/keys/_key_details.html.haml' do - element :delete_key_button + view 'app/helpers/profiles_helper.rb' do + element :delete_ssh_key_button + element :ssh_key_delete_modal end view 'app/views/profiles/keys/_key_table.html.haml' do @@ -38,8 +39,14 @@ module QA def remove_key(title) click_link(title) - accept_alert do - click_element(:delete_key_button) + click_element(:delete_ssh_key_button) + + # Retrying due to https://gitlab.com/gitlab-org/gitlab/-/issues/255287 + retry_on_exception do + wait_for_animated_element(:ssh_key_delete_modal) + within_element(:ssh_key_delete_modal) do + click_button('Delete') + end end end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 826acaa2e0a..a02617def9e 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -12,7 +12,7 @@ module QA view 'app/assets/javascripts/notes/components/comment_form.vue' do element :comment_button - element :comment_input + element :comment_field end view 'app/assets/javascripts/notes/components/discussion_filter.vue' do @@ -43,7 +43,7 @@ module QA end view 'app/assets/javascripts/related_issues/components/related_issuable_input.vue' do - element :add_issue_input + element :add_issue_field end view 'app/assets/javascripts/related_issues/components/related_issues_block.vue' do @@ -57,8 +57,8 @@ module QA def relate_issue(issue) click_element(:related_issues_plus_button) - fill_element(:add_issue_input, issue.web_url) - send_keys_to_element(:add_issue_input, :enter) + fill_element(:add_issue_field, issue.web_url) + send_keys_to_element(:add_issue_field, :enter) end def related_issuable_item @@ -84,7 +84,7 @@ module QA # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) method("select_#{filter}_filter").call - fill_element :comment_input, "#{text}\n" + fill_element :comment_field, "#{text}\n" unless attachment.nil? QA::Page::Component::Dropzone.new(self, '.new-note') @@ -125,6 +125,8 @@ module QA click_element(:title) click_element :discussion_filter find_element(:filter_options, text: text).click + + wait_for_loading end end end diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb index 0c92f9a9f28..114e3ddd46a 100644 --- a/qa/qa/page/project/operations/kubernetes/index.rb +++ b/qa/qa/page/project/operations/kubernetes/index.rb @@ -7,11 +7,11 @@ module QA module Kubernetes class Index < Page::Base view 'app/views/clusters/clusters/_empty_state.html.haml' do - element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern + element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Integrate with a cluster certificate')" # rubocop:disable QA/ElementWithPattern end def add_kubernetes_cluster - click_on 'Add Kubernetes cluster' + click_on 'Connect cluster with certificate' end def has_cluster?(cluster) diff --git a/qa/qa/page/project/packages/index.rb b/qa/qa/page/project/packages/index.rb index 6d55d1d04b6..396d3373b8a 100644 --- a/qa/qa/page/project/packages/index.rb +++ b/qa/qa/page/project/packages/index.rb @@ -26,3 +26,5 @@ module QA end end end + +QA::Page::Project::Packages::Index.prepend_if_ee('QA::EE::Page::Project::Packages::Index') diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index aa2ef2f058f..b2ecbb2e988 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -14,6 +14,10 @@ module QA element :pipeline_retry_button end + view 'app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue' do + element :run_pipeline_button + end + def click_on_latest_pipeline all_elements(:pipeline_url_link, minimum: 1, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).first.click end @@ -40,6 +44,14 @@ module QA wait_for_latest_pipeline_success end end + + def has_pipeline? + has_element? :pipeline_url_link + end + + def click_run_pipeline_button + click_element :run_pipeline_button, Page::Project::Pipeline::New + end end end end diff --git a/qa/qa/page/project/pipeline/new.rb b/qa/qa/page/project/pipeline/new.rb new file mode 100644 index 00000000000..644a21b46e9 --- /dev/null +++ b/qa/qa/page/project/pipeline/new.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Pipeline + class New < QA::Page::Base + view 'app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue' do + element :run_pipeline_button, required: true + end + + def click_run_pipeline_button + click_element :run_pipeline_button + end + end + end + end + end +end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index c607b35005e..d81be2803bd 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -103,6 +103,8 @@ module QA end def click_commit(commit_msg) + wait_for_requests + within_element(:file_tree_table) do click_on commit_msg end diff --git a/qa/qa/page/project/snippet/index.rb b/qa/qa/page/project/snippet/index.rb new file mode 100644 index 00000000000..a221abc4196 --- /dev/null +++ b/qa/qa/page/project/snippet/index.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Snippet + class Index < Page::Base + include Page::Component::Snippet + + view 'app/views/shared/snippets/_snippet.html.haml' do + element :snippet_link + end + + def has_project_snippet?(title) + has_element?(:snippet_link, snippet_title: title) + end + + def click_snippet_link(title) + within_element(:snippet_link, text: title) do + click_link(title) + end + end + end + end + end + end +end + +QA::Page::Project::Snippet::Index.prepend_if_ee('QA::EE::Page::Project::Snippet::Index') diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index d0cbab70983..034feb4e90f 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -96,15 +96,38 @@ module QA end def api_post - response = post( - Runtime::API::Request.new(api_client, api_post_path).url, - api_post_body) + if api_post_path == "/graphql" + graphql_response = post( + Runtime::API::Request.new(api_client, api_post_path).url, + query: api_post_body) - unless response.code == HTTP_STATUS_CREATED - raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`." + flattened_response = flatten_hash(parse_body(graphql_response)) + + unless graphql_response.code == HTTP_STATUS_OK && flattened_response[:errors].empty? + raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`." + end + + flattened_response[:web_url] = flattened_response.delete(:webUrl) + flattened_response[:id] = flattened_response.fetch(:id).split('/')[-1] + + process_api_response(flattened_response) + else + response = post( + Runtime::API::Request.new(api_client, api_post_path).url, + api_post_body) + + unless response.code == HTTP_STATUS_CREATED + raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`." + end + + process_api_response(parse_body(response)) end + end - process_api_response(parse_body(response)) + def flatten_hash(param) + param.each_pair.reduce({}) do |a, (k, v)| + v.is_a?(Hash) ? a.merge(flatten_hash(v)) : a.merge(k.to_sym => v) + end end def api_delete diff --git a/qa/qa/resource/design.rb b/qa/qa/resource/design.rb new file mode 100644 index 00000000000..10d4f32d49d --- /dev/null +++ b/qa/qa/resource/design.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module QA + module Resource + class Design < Base + attribute :issue do + Issue.fabricate_via_api! + end + + attribute :filepath do + ::File.absolute_path(::File.join('spec', 'fixtures', @filename)) + end + + attribute :id + attribute :filename + + def initialize + @filename = 'banana_sample.gif' + end + + # TODO This will be replaced as soon as file uploads over GraphQL are implemented + def fabricate! + issue.visit! + + Page::Project::Issue::Show.perform do |issue| + issue.add_design(filepath) + end + end + end + end +end diff --git a/qa/qa/resource/events/base.rb b/qa/qa/resource/events/base.rb index 91ec0e59e37..4c5f54825b3 100644 --- a/qa/qa/resource/events/base.rb +++ b/qa/qa/resource/events/base.rb @@ -9,9 +9,12 @@ module QA EventNotFoundError = Class.new(RuntimeError) module Base - def events(action: nil) + def events(action: nil, target_type: nil) + query = [] + query << "action=#{CGI.escape(action)}" if action + query << "target_type=#{CGI.escape(target_type)}" if target_type path = [api_get_events] - path << "?action=#{CGI.escape(action)}" if action + path << "?#{query.join("&")}" unless query.empty? parse_body(api_get_from("#{path.join}")) end diff --git a/qa/qa/resource/events/project.rb b/qa/qa/resource/events/project.rb index 99c78254f42..8c97f66c663 100644 --- a/qa/qa/resource/events/project.rb +++ b/qa/qa/resource/events/project.rb @@ -6,6 +6,13 @@ module QA module Project include Events::Base + def wait_for_merge(title) + QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_merge with title "#{title}"]) + wait_for_event do + events(action: 'accepted', target_type: 'merge_request').any? { |event| event[:target_title] == title } + end + end + def wait_for_push(commit_message) QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_push with commit message "#{commit_message}"]) wait_for_event do diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 6c0f4621dd9..dca8fb6dc6b 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'securerandom' +require 'active_support/core_ext/object/blank' module QA module Resource @@ -17,7 +18,12 @@ module QA :labels, :file_name, :file_content - attr_writer :no_preparation + attr_writer :no_preparation, + :wait_for_merge + + attribute :merge_when_pipeline_succeeds + attribute :merge_status + attribute :state attribute :project do Project.fabricate! do |resource| @@ -58,6 +64,7 @@ module QA @file_content = "File Added" @target_new_branch = true @no_preparation = false + @wait_for_merge = true end def fabricate! @@ -80,10 +87,17 @@ module QA end def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError populate(:target, :source) unless @no_preparation + super end + def api_merge_path + "/projects/#{project.id}/merge_requests/#{id}/merge" + end + def api_get_path "/projects/#{project.id}/merge_requests/#{id}" end @@ -100,6 +114,36 @@ module QA title: @title } end + + def merge_via_api! + Support::Waiter.wait_until(sleep_interval: 1) do + QA::Runtime::Logger.debug("Waiting until merge request with id '#{id}' can be merged") + + reload!.api_resource[:merge_status] == 'can_be_merged' + end + + Support::Retrier.retry_on_exception do + response = put(Runtime::API::Request.new(api_client, api_merge_path).url) + + unless response.code == HTTP_STATUS_OK + raise ResourceUpdateFailedError, "Could not merge. Request returned (#{response.code}): `#{response}`." + end + + result = parse_body(response) + + project.wait_for_merge(result[:title]) if @wait_for_merge + + result + end + end + + private + + def transform_api_resource(api_resource) + raise ResourceNotFoundError if api_resource.blank? + + super(api_resource) + end end end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 0025ebb2fd5..163c0b40bb5 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -110,6 +110,10 @@ module QA response.any? { |file| file[:path] == file_path } end + def has_branch?(branch) + has_branches?(Array(branch)) + end + def has_branches?(branches) branches.all? do |branch| response = get(Runtime::API::Request.new(api_client, "#{api_repository_branches_path}/#{branch}").url) @@ -140,6 +144,10 @@ module QA "#{api_get_path}/members" end + def api_merge_requests_path + "#{api_get_path}/merge_requests" + end + def api_runners_path "#{api_get_path}/runners" end @@ -223,6 +231,14 @@ module QA result[:import_status] end + def merge_requests + parse_body(get(Runtime::API::Request.new(api_client, api_merge_requests_path).url)) + end + + def merge_request_with_title(title) + merge_requests.find { |mr| mr[:title] == title } + end + def runners(tag_list: nil) response = if tag_list get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index 1e5399fcc59..5266f8b9bea 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -11,7 +11,7 @@ module QA :branch_name, :new_branch, :output, :repository_http_uri, :repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name - attr_writer :remote_branch, :gpg_key_id + attr_writer :remote_branch, :gpg_key_id, :merge_request_push_options def initialize @file_name = "file-#{SecureRandom.hex(8)}.txt" @@ -24,6 +24,7 @@ module QA @use_lfs = false @tag_name = nil @gpg_key_id = nil + @merge_request_push_options = nil end def remote_branch @@ -95,7 +96,7 @@ module QA end @output += commit_to repository - @output += repository.push_changes("#{branch_name}:#{remote_branch}") + @output += repository.push_changes("#{branch_name}:#{remote_branch}", push_options: @merge_request_push_options) end repository.delete_ssh_key diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb index 39be5e5cb7d..c4ea6447209 100644 --- a/qa/qa/resource/snippet.rb +++ b/qa/qa/resource/snippet.rb @@ -11,6 +11,11 @@ module QA @visibility = 'Public' @file_content = 'The snippet content' @file_name = 'New snippet file name' + @files = [] + end + + def add_files + yield @files end def fabricate! @@ -22,6 +27,12 @@ module QA new_page.set_visibility(@visibility) new_page.fill_file_name(@file_name) new_page.fill_file_content(@file_content) + + @files.each.with_index(2) do |file, i| + new_page.click_add_file + new_page.fill_file_name(file[:name], i) + new_page.fill_file_content(file[:content], i) + end new_page.click_create_snippet_button end end diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb index d4e394954ce..fcd0a479fec 100644 --- a/qa/qa/resource/ssh_key.rb +++ b/qa/qa/resource/ssh_key.rb @@ -76,6 +76,15 @@ module QA parse_body(response)[:title].include?(title) end end + + private + + def api_get + with_paginated_response_body(Runtime::API::Request.new(api_client, '/user/keys', per_page: '100').url) do |page| + key = page.find { |key| key[:title] == title } + break process_api_response(key) if key + end + end end end end diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb index 724b499d32f..b58be354103 100644 --- a/qa/qa/runtime/api/request.rb +++ b/qa/qa/runtime/api/request.rb @@ -34,7 +34,11 @@ module QA # # Returns the relative path to the requested API resource def request_path(path, version: API_VERSION, **query_string) - full_path = ::File.join('/api', version, path) + full_path = if path == '/graphql' + ::File.join('/api', path) + else + ::File.join('/api', version, path) + end if query_string.any? full_path << (path.include?('?') ? '&' : '?') diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index 579c2293c51..a48bc216ac2 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -1,98 +1,125 @@ # frozen_string_literal: true +require 'active_support/core_ext/object/blank' + module QA module Runtime - module Feature - extend self - extend Support::Api + class Feature + class << self + # Documentation: https://docs.gitlab.com/ee/api/features.html - SetFeatureError = Class.new(RuntimeError) - AuthorizationError = Class.new(RuntimeError) + include Support::Api - def enable(key) - QA::Runtime::Logger.info("Enabling feature: #{key}") - set_feature(key, true) - end + SetFeatureError = Class.new(RuntimeError) + AuthorizationError = Class.new(RuntimeError) + UnknownScopeError = Class.new(RuntimeError) - def disable(key) - QA::Runtime::Logger.info("Disabling feature: #{key}") - set_feature(key, false) - end - - def remove(key) - request = Runtime::API::Request.new(api_client, "/features/#{key}") - response = delete(request.url) - unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT - raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`." + def remove(key) + request = Runtime::API::Request.new(api_client, "/features/#{key}") + response = delete(request.url) + unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT + raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`." + end end - end - - def enable_and_verify(key) - set_and_verify(key, enable: true) - end - def disable_and_verify(key) - set_and_verify(key, enable: false) - end + def enable(key, **scopes) + set_and_verify(key, enable: true, **scopes) + end - def enabled?(key) - feature = JSON.parse(get_features).find { |flag| flag["name"] == key } - feature && feature["state"] == "on" - end + def disable(key, **scopes) + set_and_verify(key, enable: false, **scopes) + end - def get_features - request = Runtime::API::Request.new(api_client, "/features") - response = get(request.url) - response.body - end + def enabled?(key, **scopes) + feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s } + feature && feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], scopes) + end - private + private - def api_client - @api_client ||= begin - if Runtime::Env.admin_personal_access_token - Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) - else - user = Resource::User.fabricate_via_api! do |user| - user.username = Runtime::User.admin_username - user.password = Runtime::User.admin_password - end + def api_client + @api_client ||= Runtime::API::Client.as_admin + rescue Runtime::API::Client::AuthorizationError => e + raise AuthorizationError, "Administrator access is required to enable/disable feature flags. #{e.message}" + end - unless user.admin? - raise AuthorizationError, "Administrator access is required to enable/disable feature flags. User '#{user.username}' is not an administrator." + def enabled_scope?(gates, scopes) + scopes.each do |key, value| + case key + when :project, :group, :user + actors = gates.filter { |i| i['key'] == 'actors' }.first['value'] + break actors.include?("#{key.to_s.capitalize}:#{value.id}") + when :feature_group + groups = gates.filter { |i| i['key'] == 'groups' }.first['value'] + break groups.include?(value) + else + raise UnknownScopeError, "Unknown scope: #{key}" end - - Runtime::API::Client.new(:gitlab, user: user) end end - end - # Change a feature flag and verify that the change was successful - # Arguments: - # key: The feature flag to set (as a string) - # enable: `true` to enable the flag, `false` to disable it - def set_and_verify(key, enable:) - Support::Retrier.retry_on_exception(sleep_interval: 2) do - enable ? enable(key) : disable(key) + def get_features + request = Runtime::API::Request.new(api_client, '/features') + response = get(request.url) + response.body + end - is_enabled = nil + # Change a feature flag and verify that the change was successful + # Arguments: + # key: The feature flag to set (as a string) + # enable: `true` to enable the flag, `false` to disable it + # scopes: Any scope (user, project, group) to restrict the change to + def set_and_verify(key, enable:, **scopes) + msg = "#{enable ? 'En' : 'Dis'}abling feature: #{key}" + msg += " for scope \"#{scopes_to_s(scopes)}\"" if scopes.present? + QA::Runtime::Logger.info(msg) - QA::Support::Waiter.wait_until(sleep_interval: 1) do - is_enabled = enabled?(key) - is_enabled == enable - end + Support::Retrier.retry_on_exception(sleep_interval: 2) do + set_feature(key, enable, scopes) + + is_enabled = nil + + QA::Support::Waiter.wait_until(sleep_interval: 1) do + is_enabled = enabled?(key, scopes) + is_enabled == enable || !enable && scopes.present? + end + + if is_enabled == enable + QA::Runtime::Logger.info("Successfully #{enable ? 'en' : 'dis'}abled and verified feature flag: #{key}") + else + raise SetFeatureError, "#{key} was not #{enable ? 'en' : 'dis'}abled!" if enable - raise SetFeatureError, "#{key} was not #{enable ? 'enabled' : 'disabled'}!" unless is_enabled == enable + QA::Runtime::Logger.warn("Feature flag scope was removed but the flag is still enabled globally.") + end + end + end - QA::Runtime::Logger.info("Successfully #{enable ? 'enabled' : 'disabled'} and verified feature flag: #{key}") + def set_feature(key, value, **scopes) + scopes[:project] = scopes[:project].full_path if scopes.key?(:project) + scopes[:group] = scopes[:group].full_path if scopes.key?(:group) + scopes[:user] = scopes[:user].username if scopes.key?(:user) + request = Runtime::API::Request.new(api_client, "/features/#{key}") + response = post(request.url, scopes.merge({ value: value })) + unless response.code == QA::Support::Api::HTTP_STATUS_CREATED + raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`." + end end - end - def set_feature(key, value) - request = Runtime::API::Request.new(api_client, "/features/#{key}") - response = post(request.url, { value: value }) - unless response.code == QA::Support::Api::HTTP_STATUS_CREATED - raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`." + def scopes_to_s(**scopes) + key = scopes.each_key.first + s = "#{key}: " + case key + when :project, :group + s += scopes[key].full_path + when :user + s += scopes[key].username + when :feature_group + s += scopes[key] + else + raise UnknownScopeError, "Unknown scope: #{key}" + end + + s end end end diff --git a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb index 29f131ac322..c3cb503ed3f 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb @@ -17,12 +17,12 @@ module QA end before do - Runtime::Feature.enable_and_verify('gitaly_distributed_reads') + Runtime::Feature.enable(:gitaly_distributed_reads) praefect_manager.wait_for_replication(project.id) end after do - Runtime::Feature.disable_and_verify('gitaly_distributed_reads') + Runtime::Feature.disable(:gitaly_distributed_reads) end it 'reads from each node', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/979' do diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb new file mode 100644 index 00000000000..82a06780830 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merge request push options' do + # If run locally on GDK, push options need to be enabled on the host with the following command: + # + # git config --global receive.advertisepushoptions true + + branch = "push-options-test-#{SecureRandom.hex(8)}" + title = "MR push options test #{SecureRandom.hex(8)}" + commit_message = 'Add README.md' + + project = Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-request-push-options' + project.initialize_with_readme = true + end + + it 'sets labels', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1032' do + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.commit_message = commit_message + push.branch_name = branch + push.merge_request_push_options = { + create: true, + title: title, + label: %w[one two three] + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + expect(merge_request[:labels]).to include('one').and include('two').and include('three') + end + + context 'when labels are set already' do + it 'removes them', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1033' do + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_content = "Unlabel test #{SecureRandom.hex(8)}" + push.commit_message = commit_message + push.branch_name = branch + push.new_branch = false + push.merge_request_push_options = { + title: title, + unlabel: %w[one three] + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + + aggregate_failures do + expect(merge_request[:labels]).to include('two') + expect(merge_request[:labels]).not_to include('one') + expect(merge_request[:labels]).not_to include('three') + end + end + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb new file mode 100644 index 00000000000..dde4708874d --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merge request push options' do + # If run locally on GDK, push options need to be enabled on the host with the following command: + # + # git config --global receive.advertisepushoptions true + + let(:branch) { "push-options-test-#{SecureRandom.hex(8)}" } + let(:title) { "MR push options test #{SecureRandom.hex(8)}" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-request-push-options' + project.initialize_with_readme = true + end + end + + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = "runner-for-#{project.name}" + runner.tags = ["runner-for-#{project.name}"] + end + end + + after do + runner.remove_via_api! + end + + it 'sets merge when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1037' do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + no-op: + tags: + - "runner-for-#{project.name}" + script: sleep 999 # Leave the pipeline pending + YAML + } + ] + ) + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = branch + push.merge_request_push_options = { + create: true, + merge_when_pipeline_succeeds: true, + title: title + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + + merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| + mr.project = project + mr.id = merge_request[:iid] + end + + expect(merge_request.state).to eq('opened') + expect(merge_request.merge_status).to eq('checking') + expect(merge_request.merge_when_pipeline_succeeds).to be true + end + + it 'merges when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1036' do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + no-op: + tags: + - "runner-for-#{project.name}" + script: echo 'OK' + YAML + } + ] + ) + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = branch + push.merge_request_push_options = { + create: true, + merge_when_pipeline_succeeds: true, + title: title + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + expect(merge_request[:merge_when_pipeline_succeeds]).to be true + + merge_request = Support::Waiter.wait_until(sleep_interval: 5) do + mr = Resource::MergeRequest.fabricate_via_api! do |mr| + mr.project = project + mr.id = merge_request[:iid] + end + + next unless mr.state == 'merged' + + mr + end + + expect(merge_request.state).to eq('merged') + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb new file mode 100644 index 00000000000..d6bd668fa8a --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merge request push options' do + # If run locally on GDK, push options need to be enabled on the host with the following command: + # + # git config --global receive.advertisepushoptions true + + let(:branch) { "push-options-test-#{SecureRandom.hex(8)}" } + let(:title) { "MR push options test #{SecureRandom.hex(8)}" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-request-push-options' + project.initialize_with_readme = true + end + end + + it 'removes the source branch', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1035' do + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = branch + push.merge_request_push_options = { + create: true, + remove_source_branch: true, + title: title + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + + merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| + mr.project = project + mr.id = merge_request[:iid] + end.merge_via_api! + + expect(merge_request[:state]).to eq('merged') + expect(project).not_to have_branch(branch) + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb new file mode 100644 index 00000000000..6072fd8c1a2 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merge request push options' do + # If run locally on GDK, push options need to be enabled on the host with the following command: + # + # git config --global receive.advertisepushoptions true + + let(:title) { "MR push options test #{SecureRandom.hex(8)}" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-request-push-options' + project.initialize_with_readme = true + end + end + + it 'sets a target branch', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1034' do + target_branch = "push-options-test-target-#{SecureRandom.hex(8)}" + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = target_branch + push.file_content = "Target branch test target branch #{SecureRandom.hex(8)}" + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = "push-options-test-#{SecureRandom.hex(8)}" + push.file_content = "Target branch test source branch #{SecureRandom.hex(8)}" + push.merge_request_push_options = { + create: true, + title: title, + target: target_branch + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + expect(merge_request[:target_branch]).to eq(target_branch) + + merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| + mr.project = project + mr.id = merge_request[:iid] + end.merge_via_api! + + expect(merge_request[:state]).to eq('merged') + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb new file mode 100644 index 00000000000..f49a8a229dc --- /dev/null +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merge request push options' do + # If run locally on GDK, push options need to be enabled on the host with the following command: + # + # git config --global receive.advertisepushoptions true + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-request-push-options' + project.initialize_with_readme = true + end + end + + it 'sets title and description', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1038' do + description = "This is a test of MR push options" + title = "MR push options test #{SecureRandom.hex(8)}" + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = "push-options-test-#{SecureRandom.hex(8)}" + push.merge_request_push_options = { + create: true, + title: title, + description: description + } + end + + merge_request = project.merge_request_with_title(title) + + expect(merge_request).not_to be_nil, "There was a problem creating the merge request" + + aggregate_failures do + expect(merge_request[:title]).to eq(title) + expect(merge_request[:description]).to eq(description) + end + + merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| + mr.project = project + mr.id = merge_request[:iid] + end.merge_via_api! + + expect(merge_request[:state]).to eq('merged') + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb index af155b22618..f86bbee05c2 100644 --- a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb @@ -13,7 +13,7 @@ module QA Runtime::ApplicationSettings.restore_application_settings(:default_branch_name) end - it 'sets the default branch name for a new project' do + it 'sets the default branch name for a new project', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1018' do project = Resource::Project.fabricate_via_api! do |project| project.name = "default-branch-name" project.initialize_with_readme = true @@ -32,7 +32,7 @@ module QA end end - it 'allows a project to be created via the CLI with a different default branch name' do + it 'allows a project to be created via the CLI with a different default branch name', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1019' do project_name = "default-branch-name-via-cli-#{SecureRandom.hex(8)}" group = Resource::Group.fabricate_via_api! diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb index 4bd99b4820e..548933d2cde 100644 --- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -28,6 +28,13 @@ module QA end end + after do + # Delete the .netrc file created during this test so that subsequent tests don't try to use the logins + Git::Repository.perform do |repository| + repository.delete_netrc + end + end + it 'download archives of each user project then check they are different', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/427' do archive_checksums = {} diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb index bd0d28c86be..e514507fcb6 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb @@ -18,7 +18,7 @@ module QA QA::Resource::Group.fabricate_via_api! do |group| group.sandbox = sandbox_group group.api_client = owner_api_client - group.name = 'group-with-2fa' + group.path = "group-with-2fa-#{SecureRandom.hex(8)}" end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb index b011978dce6..c908b1c46a1 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan' do + RSpec.describe 'Plan', :reliable do describe 'Milestones' do include Support::Dates diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb index 5f7e28190b2..564cfbb8399 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan' do + RSpec.describe 'Plan', :reliable do describe 'Group milestone' do include Support::Dates diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb index 489691b4d0c..99d547acb26 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan' do + RSpec.describe 'Plan', :reliable do describe 'Project milestone' do include Support::Dates diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb new file mode 100644 index 00000000000..7090427e5a4 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Design Management' do + let(:first_design) { Resource::Design.fabricate! } + + let(:second_design) do + Resource::Design.fabricate! do |design| + design.issue = first_design.issue + design.filename = 'values.png' + end + end + + let(:third_design) do + Resource::Design.fabricate! do |design| + design.issue = second_design.issue + design.filename = 'tanuki.jpg' + end + end + + before do + Flow::Login.sign_in + end + + it 'user archives a design', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/274' do + third_design.issue.visit! + + Page::Project::Issue::Show.perform do |issue| + issue.select_design(third_design.filename) + + issue.archive_selected_designs + + expect(issue).not_to have_design(third_design.filename) + expect(issue).to have_design(first_design.filename) + expect(issue).to have_design(second_design.filename) + end + + Page::Project::Issue::Show.perform do |issue| + issue.select_design(second_design.filename) + issue.select_design(first_design.filename) + + issue.archive_selected_designs + + expect(issue).not_to have_design(first_design.filename) + expect(issue).not_to have_design(second_design.filename) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb new file mode 100644 index 00000000000..7844d0d7ccb --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create', :requires_admin do + describe 'View merge request merge-ref diff' do + let(:merge_request) do + Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.title = 'This is a merge request' + merge_request.description = '... for viewing merge-ref and merge-base diffs' + merge_request.file_content = 'This exists on the source branch only' + end + end + + let(:new_file_name) { "added_file-#{SecureRandom.hex(8)}.txt" } + + before do + commit_to_branch(merge_request.target_branch, new_file_name) + commit_to_branch(merge_request.source_branch, new_file_name) + + Flow::Login.sign_in + end + + context 'when the feature flag default_merge_ref_for_diffs is enabled' do + before do + Runtime::Feature.enable('default_merge_ref_for_diffs', project: merge_request.project) + + merge_request.visit! + end + + it 'views the merge-ref diff by default' do + Page::MergeRequest::Show.perform do |mr_page| + mr_page.click_diffs_tab + mr_page.click_target_version_dropdown + + expect(mr_page.version_dropdown_content).to include('master (HEAD)') + expect(mr_page.version_dropdown_content).not_to include('master (base)') + expect(mr_page).to have_file(merge_request.file_name) + expect(mr_page).not_to have_file(new_file_name) + end + end + end + + context 'when the feature flag default_merge_ref_for_diffs is disabled' do + before do + Runtime::Feature.disable('default_merge_ref_for_diffs', project: merge_request.project) + + merge_request.visit! + end + + it 'views the merge-base diff by default' do + Page::MergeRequest::Show.perform do |mr_page| + mr_page.click_diffs_tab + mr_page.click_target_version_dropdown + + expect(mr_page.version_dropdown_content).to include('master (HEAD)') + expect(mr_page.version_dropdown_content).to include('master (base)') + expect(mr_page).to have_file(merge_request.file_name) + expect(mr_page).to have_file(new_file_name) + end + end + end + + def commit_to_branch(branch, file) + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = merge_request.project + commit.branch = branch + commit.commit_message = "Add new file on #{branch}" + commit.add_files( + [ + { + file_path: file, + content: "This exists on source and target branches" + } + ] + ) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb index f96b424d233..83945a09587 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb @@ -3,8 +3,8 @@ module QA RSpec.describe 'Create' do describe 'Push mirror a repository over HTTP' do - it 'configures and syncs LFS objects for a (push) mirrored repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do - Runtime::Feature.enable_and_verify('push_mirror_syncs_lfs') + it 'configures and syncs LFS objects for a (push) mirrored repository', :requires_admin, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do + Runtime::Feature.enable(:push_mirror_syncs_lfs) Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb index ef3b315506f..a85efe5829b 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb @@ -36,6 +36,10 @@ module QA Flow::Login.sign_in end + after do + ssh_key.remove_via_api! + end + it 'clones, pushes, and pulls a snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/826' do Resource::Repository::Push.fabricate! do |push| push.repository_http_uri = repository_uri_http diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb index 34f6b464f29..9789f7a1716 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb @@ -36,6 +36,10 @@ module QA Flow::Login.sign_in end + after do + ssh_key.remove_via_api! + end + it 'clones, pushes, and pulls a project snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/833' do Resource::Repository::Push.fabricate! do |push| push.repository_http_uri = repository_uri_http diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb new file mode 100644 index 00000000000..376ce51273e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create', :requires_admin do + describe 'Multiple file snippet' do + before do + Runtime::Feature.enable('snippet_multiple_files') + end + + after do + Runtime::Feature.disable('snippet_multiple_files') + end + + it 'creates a personal snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/842' do + Flow::Login.sign_in + + Page::Main::Menu.perform do |menu| + menu.go_to_more_dropdown_option(:snippets_link) + end + + Resource::Snippet.fabricate_via_browser_ui! do |snippet| + snippet.title = 'Personal snippet with multiple files' + snippet.description = 'Snippet description' + snippet.visibility = 'Public' + snippet.file_name = 'First file name' + snippet.file_content = 'First file content' + + snippet.add_files do |files| + files.append(name: 'Second file name', content: 'Second file content') + files.append(name: 'Third file name', content: 'Third file content') + end + end + + Page::Dashboard::Snippet::Show.perform do |snippet| + expect(snippet).to have_snippet_title('Personal snippet with multiple files') + expect(snippet).to have_snippet_description('Snippet description') + expect(snippet).to have_visibility_type(/public/i) + expect(snippet).to have_file_name('First file name', 1) + expect(snippet).to have_file_content('First file content', 1) + expect(snippet).to have_file_name('Second file name', 2) + expect(snippet).to have_file_content('Second file content', 2) + expect(snippet).to have_file_name('Third file name', 3) + expect(snippet).to have_file_content('Third file content', 3) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb index 0a8f6e13b2e..d80fc4c5b95 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb @@ -21,8 +21,8 @@ module QA expect(snippet).to have_file_name('markdown_file.md') expect(snippet).to have_file_content('Snippet heading') expect(snippet).to have_file_content('Gitlab link') - expect(snippet).not_to have_file_content('###') - expect(snippet).not_to have_file_content('https://gitlab.com/') + expect(snippet).to have_no_file_content('###') + expect(snippet).to have_no_file_content('https://gitlab.com/') end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb index e8053600930..4f1d9ac1696 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb @@ -4,7 +4,7 @@ module QA RSpec.describe 'Create' do context 'Wiki' do let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } - let(:new_path) { "a/new/path" } + let(:new_path) { "a/new/path-with-spaces" } before do Flow::Login.sign_in @@ -23,7 +23,9 @@ module QA Page::Project::Wiki::Edit.perform(&:click_save_changes) Page::Project::Wiki::Show.perform do |wiki| - expect(wiki).to have_directory(new_path) + expect(wiki).to have_directory('a') + expect(wiki).to have_directory('new') + expect(wiki).to have_directory('path with spaces') end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 326647b25f7..8de739f1559 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Pipeline creation and processing' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let(:max_wait) { 30 } diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb new file mode 100644 index 00000000000..153ccafaa20 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify' do + describe 'Run pipeline', :requires_admin, :skip_live_env do + # [TODO]: Developer to remove :requires_admin and :skip_live_env once FF is removed in https://gitlab.com/gitlab-org/gitlab/-/issues/229632 + + context 'with web only rule' do + let(:feature_flag) { :new_pipeline_form } + let(:job_name) { 'test_job' } + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'web-only-pipeline' + end + end + + let!(:ci_file) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + #{job_name}: + tags: + - #{project.name} + script: echo 'OK' + only: + - web + YAML + } + ] + ) + end + end + + before do + Runtime::Feature.enable(feature_flag) # [TODO]: Developer to remove when feature flag is removed + Flow::Login.sign_in + project.visit! + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + end + + after do + Runtime::Feature.disable(feature_flag) # [TODO]: Developer to remove when feature flag is removed + end + + it 'can trigger pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/946' do + Page::Project::Pipeline::Index.perform do |index| + expect(index).not_to have_pipeline # should not auto trigger pipeline + index.click_run_pipeline_button + end + + Page::Project::Pipeline::New.perform(&:click_run_pipeline_button) + + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_job(job_name) + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index a296d60b27c..9ce87f353d0 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Runner registration' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let!(:runner) do diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb index f4edaaa84a8..5bfc88e45f2 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :docker, :runner do + RSpec.describe 'Verify', :runner do describe 'Code coverage statistics' do let(:simplecov) { '\(\d+.\d+\%\) covered' } let(:executor) { "qa-runner-#{Time.now.to_i}" } diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb index a617f3b3e29..4ca356c9b65 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :docker, :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages do describe 'Maven Repository' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb index e97ede35610..43c708093b3 100644 --- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :docker, :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages do describe 'NPM registry' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 18eb52830a2..abac4f2b91d 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -3,7 +3,7 @@ require 'digest/sha1' module QA - RSpec.describe 'Release', :docker, :runner do + RSpec.describe 'Release', :runner do describe 'Git clone using a deploy key' do before do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb index 47a1b3b5670..ece45d093a7 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Release', :docker, :runner, :reliable do + RSpec.describe 'Release', :runner, :reliable do describe 'Parent-child pipelines dependent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb index 9eb81244aa4..38cee0e62ca 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Release', :docker, :runner, :reliable do + RSpec.describe 'Release', :runner, :reliable do describe 'Parent-child pipelines independent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 53a1c8010af..6d31780f196 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -15,7 +15,7 @@ module QA disable_optional_jobs(project) end - describe 'Auto DevOps support', :orchestrated, :kubernetes, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/230927', type: :stale } do + describe 'Auto DevOps support', :orchestrated, :kubernetes, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/251090', type: :stale } do context 'when rbac is enabled' do let(:cluster) { Service::KubernetesCluster.new.create! } @@ -24,6 +24,8 @@ module QA end it 'runs auto devops', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/702' do + skip('Test requires tunnel: see https://gitlab.com/gitlab-org/gitlab/-/issues/251090') + Flow::Login.sign_in # Set an application secret CI variable (prefixed with K8S_SECRET_) diff --git a/qa/qa/specs/features/sanity/framework_spec.rb b/qa/qa/specs/features/sanity/framework_spec.rb index 611c6c7b1ff..feec56478c0 100644 --- a/qa/qa/specs/features/sanity/framework_spec.rb +++ b/qa/qa/specs/features/sanity/framework_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Framework sanity checks', :orchestrated, :framework do + RSpec.describe 'Framework sanity checks', :orchestrated, :framework do describe 'Passing orchestrated example' do it 'succeeds' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index 08faacb6db3..5fc36b68e5c 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -40,7 +40,7 @@ module QA return_response_or_raise(e) end - def put(url, payload) + def put(url, payload = nil) RestClient::Request.execute( method: :put, url: url, diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb index 943d7d510df..ebc473a7d86 100644 --- a/qa/qa/support/wait_for_requests.rb +++ b/qa/qa/support/wait_for_requests.rb @@ -9,18 +9,14 @@ module QA def wait_for_requests(skip_finished_loading_check: false) Waiter.wait_until(log: false) do - finished_all_ajax_requests? && finished_all_axios_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true) + finished_all_ajax_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true) end - end - - def finished_all_axios_requests? - Capybara.page.evaluate_script('window.pendingRequests || 0').zero? # rubocop:disable Style/NumericPredicate + rescue Repeater::WaitExceededError + raise $!, 'Page did not fully load. This could be due to an unending async request or loading icon.' end def finished_all_ajax_requests? - return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"') - - Capybara.page.evaluate_script('jQuery.active').zero? # rubocop:disable Style/NumericPredicate + Capybara.page.evaluate_script('window.pendingRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate end def finished_loading?(wait: DEFAULT_MAX_WAIT_TIME) diff --git a/qa/spec/factory/resource/user_spec.rb b/qa/spec/factory/resource/user_spec.rb index d59ee24c758..1adf3799b0e 100644 --- a/qa/spec/factory/resource/user_spec.rb +++ b/qa/spec/factory/resource/user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::User do +RSpec.describe QA::Resource::User do describe "#fabricate_via_api!" do response = Struct.new(:code, :body) diff --git a/qa/spec/fixtures/tanuki.jpg b/qa/spec/fixtures/tanuki.jpg Binary files differnew file mode 100644 index 00000000000..f0df472663e --- /dev/null +++ b/qa/spec/fixtures/tanuki.jpg diff --git a/qa/spec/fixtures/values.png b/qa/spec/fixtures/values.png Binary files differnew file mode 100644 index 00000000000..9ecb6e7b778 --- /dev/null +++ b/qa/spec/fixtures/values.png diff --git a/qa/spec/git/location_spec.rb b/qa/spec/git/location_spec.rb index 0c57291666f..ee714206e4f 100644 --- a/qa/spec/git/location_spec.rb +++ b/qa/spec/git/location_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Git::Location do +RSpec.describe QA::Git::Location do describe '.new' do context 'when URI starts with ssh://' do context 'when URI has port' do diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index 8355c77f493..2ac7a99d82f 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Git::Repository do +RSpec.describe QA::Git::Repository do include Helpers::StubENV shared_context 'unresolvable git directory' do @@ -117,6 +117,66 @@ describe QA::Git::Repository do let(:call_method) { repository.push_changes(branch) } end end + + context 'with push options' do + let(:command) { "git push #{push_options} #{repo_uri_with_credentials} #{branch}" } + + context 'when set to create a merge request' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.create' } + let(:call_method) { repository.push_changes(push_options: { create: true }) } + end + end + + context 'when set to merge when pipeline succeeds' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.merge_when_pipeline_succeeds' } + let(:call_method) { repository.push_changes(push_options: { merge_when_pipeline_succeeds: true }) } + end + end + + context 'when set to remove source branch' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.remove_source_branch' } + let(:call_method) { repository.push_changes(push_options: { remove_source_branch: true }) } + end + end + + context 'when title is given' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.title="Is A Title"' } + let(:call_method) { repository.push_changes(push_options: { title: 'Is A Title' }) } + end + end + + context 'when description is given' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.description="Is A Description"' } + let(:call_method) { repository.push_changes(push_options: { description: 'Is A Description' }) } + end + end + + context 'when target branch is given' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.target="is-a-target-branch"' } + let(:call_method) { repository.push_changes(push_options: { target: 'is-a-target-branch' }) } + end + end + + context 'when a label is given' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.label="is-a-label"' } + let(:call_method) { repository.push_changes(push_options: { label: ['is-a-label'] }) } + end + end + + context 'when two labels are given' do + it_behaves_like 'command with retries' do + let(:push_options) { '-o merge_request.label="is-a-label" -o merge_request.label="is-another-label"' } + let(:call_method) { repository.push_changes(push_options: { label: %w[is-a-label is-another-label] }) } + end + end + end end describe '#git_protocol=' do diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 0cbb0a2b12e..52345876149 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Page::Base do +RSpec.describe QA::Page::Base do describe 'page helpers' do it 'exposes helpful page helpers' do expect(subject).to respond_to :refresh, :wait_until, :scroll_to diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index 3f64743ffac..fbf58b5e18a 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Page::Element do +RSpec.describe QA::Page::Element do describe '#selector' do it 'transforms element name into QA-specific selector' do expect(described_class.new(:sign_in_button).selector) diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index f2ce9fb2cf8..df3447770be 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -3,7 +3,7 @@ require 'capybara/dsl' require 'logger' -describe QA::Support::Page::Logging do +RSpec.describe QA::Support::Page::Logging do let(:page) { double.as_null_object } before do diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb index c727cfb686e..cfb36052294 100644 --- a/qa/spec/page/validator_spec.rb +++ b/qa/spec/page/validator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Page::Validator do +RSpec.describe QA::Page::Validator do describe '#constants' do subject do described_class.new(QA::Page::Project) diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index 3cb64dcd9c2..3342b387ed1 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Page::View do +RSpec.describe QA::Page::View do let(:element) do double('element', name: :something, pattern: /some element/) end diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb index eb2bdd1be64..69a95c92332 100644 --- a/qa/spec/resource/api_fabricator_spec.rb +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::ApiFabricator do +RSpec.describe QA::Resource::ApiFabricator do let(:resource_without_api_support) do Class.new do def self.name diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb index b23de19e1f8..c0bedf794be 100644 --- a/qa/spec/resource/base_spec.rb +++ b/qa/spec/resource/base_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::Base do +RSpec.describe QA::Resource::Base do include Helpers::StubENV let(:resource) { spy('resource') } diff --git a/qa/spec/resource/events/base_spec.rb b/qa/spec/resource/events/base_spec.rb index 9cdf4785092..4df30a970fc 100644 --- a/qa/spec/resource/events/base_spec.rb +++ b/qa/spec/resource/events/base_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::Events::Base do +RSpec.describe QA::Resource::Events::Base do let(:resource) do Class.new(QA::Resource::Base) do def api_get_path diff --git a/qa/spec/resource/events/project_spec.rb b/qa/spec/resource/events/project_spec.rb index 98da87906fa..88d50749a0a 100644 --- a/qa/spec/resource/events/project_spec.rb +++ b/qa/spec/resource/events/project_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::Events::Project do +RSpec.describe QA::Resource::Events::Project do let(:resource) do Class.new(QA::Resource::Base) do def api_get_path diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb index 2f9e4958ae1..2b9c90b3dac 100644 --- a/qa/spec/resource/repository/push_spec.rb +++ b/qa/spec/resource/repository/push_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::Repository::Push do +RSpec.describe QA::Resource::Repository::Push do describe '.files=' do let(:files) do [ diff --git a/qa/spec/resource/ssh_key_spec.rb b/qa/spec/resource/ssh_key_spec.rb index b2b5ec070e1..fd0fda3c1b8 100644 --- a/qa/spec/resource/ssh_key_spec.rb +++ b/qa/spec/resource/ssh_key_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::SSHKey do +RSpec.describe QA::Resource::SSHKey do describe '#key' do it 'generates a default key' do expect(subject.key).to be_a(QA::Runtime::Key::RSA) diff --git a/qa/spec/resource/user_spec.rb b/qa/spec/resource/user_spec.rb index 5845f7996a3..e7397d9c0bf 100644 --- a/qa/spec/resource/user_spec.rb +++ b/qa/spec/resource/user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Resource::User do +RSpec.describe QA::Resource::User do let(:api_resource) do { name: "GitLab QA", diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb index 6f7020d6595..dd139fda980 100644 --- a/qa/spec/runtime/api/client_spec.rb +++ b/qa/spec/runtime/api/client_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::API::Client do +RSpec.describe QA::Runtime::API::Client do include Helpers::StubENV describe 'initialization' do diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb index 8354eff6234..93de2f4a87e 100644 --- a/qa/spec/runtime/api/request_spec.rb +++ b/qa/spec/runtime/api/request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::API::Request do +RSpec.describe QA::Runtime::API::Request do let(:client) { QA::Runtime::API::Client.new('http://example.com') } let(:request) { described_class.new(client, '/users') } diff --git a/qa/spec/runtime/application_settings_spec.rb b/qa/spec/runtime/application_settings_spec.rb index e48214b22e6..5c4947f6f70 100644 --- a/qa/spec/runtime/application_settings_spec.rb +++ b/qa/spec/runtime/application_settings_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::ApplicationSettings do +RSpec.describe QA::Runtime::ApplicationSettings do let(:api_client) { double('QA::Runtime::API::Client') } let(:request) { Struct.new(:url).new('http://api') } let(:get_response) { Struct.new(:body).new("{}") } diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 0cfb9a70c88..bf6c01bf251 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Env do +RSpec.describe QA::Runtime::Env do include Helpers::StubENV shared_examples 'boolean method' do |**kwargs| diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb index db3c2f65963..39c20dd3070 100644 --- a/qa/spec/runtime/feature_spec.rb +++ b/qa/spec/runtime/feature_spec.rb @@ -1,87 +1,215 @@ # frozen_string_literal: true -describe QA::Runtime::Feature do +RSpec.describe QA::Runtime::Feature do let(:api_client) { double('QA::Runtime::API::Client') } let(:request) { Struct.new(:url).new('http://api') } let(:response_post) { Struct.new(:code).new(201) } - let(:response_get) { Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "on" }]') } before do allow(described_class).to receive(:api_client).and_return(api_client) end - describe '.enable' do - it 'enables a feature flag' do - expect(QA::Runtime::API::Request) - .to receive(:new) - .with(api_client, "/features/a-flag") - .and_return(request) - expect(described_class) - .to receive(:post) - .with(request.url, { value: true }) - .and_return(response_post) - - subject.enable('a-flag') - end + where(:feature_flag) do + ['a_flag', :a_flag] end - describe '.enable_and_verify' do - it 'enables a feature flag' do - allow(described_class).to receive(:get).and_return(response_get) + with_them do + shared_examples 'enables a feature flag' do + it 'enables a feature flag for a scope' do + allow(described_class).to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]')) - expect(QA::Runtime::API::Request).to receive(:new) - .with(api_client, "/features/a-flag").and_return(request) - expect(described_class).to receive(:post) - .with(request.url, { value: true }).and_return(response_post) - expect(QA::Runtime::API::Request).to receive(:new) - .with(api_client, "/features").and_return(request) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a_flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: true, scope => actor_name }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + expect(QA::Runtime::Logger).to receive(:info).with("Enabling feature: a_flag for scope \"#{scope}: #{actor_name}\"") + expect(QA::Runtime::Logger).to receive(:info).with("Successfully enabled and verified feature flag: a_flag") - subject.enable_and_verify('a-flag') + described_class.enable(feature_flag, scope => actor) + end end - end - describe '.disable' do - it 'disables a feature flag' do - expect(QA::Runtime::API::Request) - .to receive(:new) - .with(api_client, "/features/a-flag") - .and_return(request) - expect(described_class) - .to receive(:post) - .with(request.url, { value: false }) - .and_return(response_post) - - subject.disable('a-flag') + shared_examples 'disables a feature flag' do + it 'disables a feature flag for a scope' do + allow(described_class).to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]')) + + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a_flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: false, scope => actor_name }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + expect(QA::Runtime::Logger).to receive(:info).with("Disabling feature: a_flag for scope \"#{scope}: #{actor_name}\"") + expect(QA::Runtime::Logger).to receive(:info).with("Successfully disabled and verified feature flag: a_flag") + + described_class.disable(feature_flag, scope => actor ) + end end - end - describe '.disable_and_verify' do - it 'disables a feature flag' do - allow(described_class).to receive(:get) - .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "off" }]')) + shared_examples 'checks a feature flag' do + context 'when the flag is enabled for a scope' do + it 'returns the feature flag state' do + expect(QA::Runtime::API::Request) + .to receive(:new) + .with(api_client, "/features") + .and_return(request) + expect(described_class) + .to receive(:get) + .and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }]))) + + expect(described_class.enabled?(feature_flag, scope => actor)).to be_truthy + end + end + end - expect(QA::Runtime::API::Request).to receive(:new) - .with(api_client, "/features/a-flag").and_return(request) - expect(described_class).to receive(:post) - .with(request.url, { value: false }).and_return(response_post) - expect(QA::Runtime::API::Request).to receive(:new) - .with(api_client, "/features").and_return(request) + describe '.enable' do + it 'enables a feature flag' do + allow(described_class).to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]')) - subject.disable_and_verify('a-flag') + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a_flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: true }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + + described_class.enable(feature_flag) + end + + context 'when a project scope is provided' do + it_behaves_like 'enables a feature flag' do + let(:scope) { :project } + let(:actor_name) { 'group-name/project-name' } + let(:actor) { Struct.new(:full_path).new(actor_name) } + end + end + + context 'when a group scope is provided' do + it_behaves_like 'enables a feature flag' do + let(:scope) { :group } + let(:actor_name) { 'group-name' } + let(:actor) { Struct.new(:full_path).new(actor_name) } + end + end + + context 'when a user scope is provided' do + it_behaves_like 'enables a feature flag' do + let(:scope) { :user } + let(:actor_name) { 'user-name' } + let(:actor) { Struct.new(:username).new(actor_name) } + end + end + + context 'when a feature group scope is provided' do + it_behaves_like 'enables a feature flag' do + let(:scope) { :feature_group } + let(:actor_name) { 'foo' } + let(:actor) { "foo" } + end + end end - end - describe '.enabled?' do - it 'returns a feature flag state' do - expect(QA::Runtime::API::Request) - .to receive(:new) - .with(api_client, "/features") - .and_return(request) - expect(described_class) - .to receive(:get) - .and_return(response_get) - - expect(subject.enabled?('a-flag')).to be_truthy + describe '.disable' do + it 'disables a feature flag' do + allow(described_class).to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]')) + + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a_flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: false }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + + described_class.disable(feature_flag) + end + + context 'when a project scope is provided' do + it_behaves_like 'disables a feature flag' do + let(:scope) { :project } + let(:actor_name) { 'group-name/project-name' } + let(:actor) { Struct.new(:full_path).new(actor_name) } + end + end + + context 'when a group scope is provided' do + it_behaves_like 'disables a feature flag' do + let(:scope) { :group } + let(:actor_name) { 'group-name' } + let(:actor) { Struct.new(:full_path).new(actor_name) } + end + end + + context 'when a user scope is provided' do + it_behaves_like 'disables a feature flag' do + let(:scope) { :user } + let(:actor_name) { 'user-name' } + let(:actor) { Struct.new(:username).new(actor_name) } + end + end + + context 'when a feature group scope is provided' do + it_behaves_like 'disables a feature flag' do + let(:scope) { :feature_group } + let(:actor_name) { 'foo' } + let(:actor) { "foo" } + end + end + end + + describe '.enabled?' do + it 'returns a feature flag state' do + expect(QA::Runtime::API::Request) + .to receive(:new) + .with(api_client, "/features") + .and_return(request) + expect(described_class) + .to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]')) + + expect(described_class.enabled?(feature_flag)).to be_truthy + end + + context 'when a project scope is provided' do + it_behaves_like 'checks a feature flag' do + let(:scope) { :project } + let(:actor_name) { 'group-name/project-name' } + let(:actor) { Struct.new(:full_path, :id).new(actor_name, 270) } + let(:gates) { %q([{"key": "actors", "value": ["Project:270"]}]) } + end + end + + context 'when a group scope is provided' do + it_behaves_like 'checks a feature flag' do + let(:scope) { :group } + let(:actor_name) { 'group-name' } + let(:actor) { Struct.new(:full_path, :id).new(actor_name, 33) } + let(:gates) { %q([{"key": "actors", "value": ["Group:33"]}]) } + end + end + + context 'when a user scope is provided' do + it_behaves_like 'checks a feature flag' do + let(:scope) { :user } + let(:actor_name) { 'user-name' } + let(:actor) { Struct.new(:full_path, :id).new(actor_name, 13) } + let(:gates) { %q([{"key": "actors", "value": ["User:13"]}]) } + end + end + + context 'when a feature group scope is provided' do + it_behaves_like 'checks a feature flag' do + let(:scope) { :feature_group } + let(:actor_name) { 'foo' } + let(:actor) { "foo" } + let(:gates) { %q([{"key": "groups", "value": ["foo"]}]) } + end + end end end end diff --git a/qa/spec/runtime/key/ecdsa_spec.rb b/qa/spec/runtime/key/ecdsa_spec.rb index 3f9718e62c5..499233df618 100644 --- a/qa/spec/runtime/key/ecdsa_spec.rb +++ b/qa/spec/runtime/key/ecdsa_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Key::ECDSA do +RSpec.describe QA::Runtime::Key::ECDSA do describe '#public_key' do [256, 384, 521].each do |bits| it "generates a public #{bits}-bits ECDSA key" do diff --git a/qa/spec/runtime/key/ed25519_spec.rb b/qa/spec/runtime/key/ed25519_spec.rb index 08f232260af..e63c7f5deae 100644 --- a/qa/spec/runtime/key/ed25519_spec.rb +++ b/qa/spec/runtime/key/ed25519_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Key::ED25519 do +RSpec.describe QA::Runtime::Key::ED25519 do describe '#public_key' do subject { described_class.new.public_key } diff --git a/qa/spec/runtime/key/rsa_spec.rb b/qa/spec/runtime/key/rsa_spec.rb index fcb52f541bf..5b5d8a13fa1 100644 --- a/qa/spec/runtime/key/rsa_spec.rb +++ b/qa/spec/runtime/key/rsa_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Key::RSA do +RSpec.describe QA::Runtime::Key::RSA do describe '#public_key' do subject { described_class.new.public_key } diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb index 44be3381bff..a888bf1452b 100644 --- a/qa/spec/runtime/logger_spec.rb +++ b/qa/spec/runtime/logger_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Logger do +RSpec.describe QA::Runtime::Logger do before do logger = Logger.new $stdout logger.level = ::Logger::DEBUG diff --git a/qa/spec/runtime/namespace_spec.rb b/qa/spec/runtime/namespace_spec.rb index d24fa509f30..92836862864 100644 --- a/qa/spec/runtime/namespace_spec.rb +++ b/qa/spec/runtime/namespace_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Namespace do +RSpec.describe QA::Runtime::Namespace do include Helpers::StubENV describe '.name' do diff --git a/qa/spec/runtime/release_spec.rb b/qa/spec/runtime/release_spec.rb index b5a7dd5269d..b4e278fb546 100644 --- a/qa/spec/runtime/release_spec.rb +++ b/qa/spec/runtime/release_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Release do +RSpec.describe QA::Runtime::Release do context 'when release version has extension strategy' do let(:strategy) { spy('strategy') } diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb index 30ada4529ed..175973b6795 100644 --- a/qa/spec/runtime/scenario_spec.rb +++ b/qa/spec/runtime/scenario_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Runtime::Scenario do +RSpec.describe QA::Runtime::Scenario do subject do Module.new.extend(described_class) end diff --git a/qa/spec/scenario/actable_spec.rb b/qa/spec/scenario/actable_spec.rb index 589d0c61993..36e9f3de961 100644 --- a/qa/spec/scenario/actable_spec.rb +++ b/qa/spec/scenario/actable_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Actable do +RSpec.describe QA::Scenario::Actable do subject do Class.new do include QA::Scenario::Actable diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb index e8accb45518..8a96e9bebbf 100644 --- a/qa/spec/scenario/bootable_spec.rb +++ b/qa/spec/scenario/bootable_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Bootable do +RSpec.describe QA::Scenario::Bootable do subject do Class.new(QA::Scenario::Template) .include(described_class) diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb index 65793734548..f07d817ea16 100644 --- a/qa/spec/scenario/template_spec.rb +++ b/qa/spec/scenario/template_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Template do +RSpec.describe QA::Scenario::Template do let(:feature) { spy('Runtime::Feature') } let(:release) { spy('Runtime::Release') } diff --git a/qa/spec/scenario/test/instance/airgapped_spec.rb b/qa/spec/scenario/test/instance/airgapped_spec.rb index 0c4167eafff..5e319ba4bbb 100644 --- a/qa/spec/scenario/test/instance/airgapped_spec.rb +++ b/qa/spec/scenario/test/instance/airgapped_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Instance::Airgapped do +RSpec.describe QA::Scenario::Test::Instance::Airgapped do describe '#perform' do it_behaves_like 'a QA scenario class' do end diff --git a/qa/spec/scenario/test/instance/all_spec.rb b/qa/spec/scenario/test/instance/all_spec.rb index 8acd56914c5..875df9a32f5 100644 --- a/qa/spec/scenario/test/instance/all_spec.rb +++ b/qa/spec/scenario/test/instance/all_spec.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Instance::All do +RSpec.describe QA::Scenario::Test::Instance::All do it_behaves_like 'a QA scenario class' end diff --git a/qa/spec/scenario/test/instance/smoke_spec.rb b/qa/spec/scenario/test/instance/smoke_spec.rb index 6cc71699be9..09d0df2c479 100644 --- a/qa/spec/scenario/test/instance/smoke_spec.rb +++ b/qa/spec/scenario/test/instance/smoke_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Instance::Smoke do +RSpec.describe QA::Scenario::Test::Instance::Smoke do it_behaves_like 'a QA scenario class' do let(:tags) { [:smoke] } end diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb index b2d577bd552..b68b06a7b9f 100644 --- a/qa/spec/scenario/test/integration/github_spec.rb +++ b/qa/spec/scenario/test/integration/github_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::Github do +RSpec.describe QA::Scenario::Test::Integration::Github do describe '#perform' do let(:env) { spy('Runtime::Env') } diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb index 15f15b2e643..20e860d3e4b 100644 --- a/qa/spec/scenario/test/integration/instance_saml_spec.rb +++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::InstanceSAML do +RSpec.describe QA::Scenario::Test::Integration::InstanceSAML do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:instance_saml] } diff --git a/qa/spec/scenario/test/integration/kubernetes_spec.rb b/qa/spec/scenario/test/integration/kubernetes_spec.rb index 51ee7b9acff..d5885b97343 100644 --- a/qa/spec/scenario/test/integration/kubernetes_spec.rb +++ b/qa/spec/scenario/test/integration/kubernetes_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::Kubernetes do +RSpec.describe QA::Scenario::Test::Integration::Kubernetes do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:kubernetes] } diff --git a/qa/spec/scenario/test/integration/ldap_spec.rb b/qa/spec/scenario/test/integration/ldap_spec.rb index c493cde6c7a..c53302d9bd3 100644 --- a/qa/spec/scenario/test/integration/ldap_spec.rb +++ b/qa/spec/scenario/test/integration/ldap_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::LDAPNoTLS do +RSpec.describe QA::Scenario::Test::Integration::LDAPNoTLS do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_tls] } @@ -8,7 +8,7 @@ describe QA::Scenario::Test::Integration::LDAPNoTLS do end end -describe QA::Scenario::Test::Integration::LDAPNoServer do +RSpec.describe QA::Scenario::Test::Integration::LDAPNoServer do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_server] } @@ -16,7 +16,7 @@ describe QA::Scenario::Test::Integration::LDAPNoServer do end end -describe QA::Scenario::Test::Integration::LDAPTLS do +RSpec.describe QA::Scenario::Test::Integration::LDAPTLS do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_tls] } diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb index 7e4eb6284e8..9532ec35b95 100644 --- a/qa/spec/scenario/test/integration/mattermost_spec.rb +++ b/qa/spec/scenario/test/integration/mattermost_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::Mattermost do +RSpec.describe QA::Scenario::Test::Integration::Mattermost do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:args) { %w[gitlab_address mattermost_address] } diff --git a/qa/spec/scenario/test/integration/object_storage_spec.rb b/qa/spec/scenario/test/integration/object_storage_spec.rb index 8b4367bee32..235dd495687 100644 --- a/qa/spec/scenario/test/integration/object_storage_spec.rb +++ b/qa/spec/scenario/test/integration/object_storage_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Integration::ObjectStorage do +RSpec.describe QA::Scenario::Test::Integration::ObjectStorage do describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:object_storage] } diff --git a/qa/spec/scenario/test/sanity/framework_spec.rb b/qa/spec/scenario/test/sanity/framework_spec.rb index a63c59e2995..5ae8b123ec2 100644 --- a/qa/spec/scenario/test/sanity/framework_spec.rb +++ b/qa/spec/scenario/test/sanity/framework_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Sanity::Framework do +RSpec.describe QA::Scenario::Test::Sanity::Framework do it_behaves_like 'a QA scenario class' do let(:tags) { [:framework] } end diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb index e18babaed63..2a68dd23219 100644 --- a/qa/spec/scenario/test/sanity/selectors_spec.rb +++ b/qa/spec/scenario/test/sanity/selectors_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Scenario::Test::Sanity::Selectors do +RSpec.describe QA::Scenario::Test::Sanity::Selectors do let(:validator) { spy('validator') } before do diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb index 9686a9771c4..b0b7d4e6804 100644 --- a/qa/spec/specs/helpers/quarantine_spec.rb +++ b/qa/spec/specs/helpers/quarantine_spec.rb @@ -36,7 +36,7 @@ RSpec.configure do |c| end end -describe QA::Specs::Helpers::Quarantine do +RSpec.describe QA::Specs::Helpers::Quarantine do describe '.skip_or_run_quarantined_contexts' do context 'with no tag focused' do before do diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb index 67d94a1f648..c2d28bf81fb 100644 --- a/qa/spec/specs/parallel_runner_spec.rb +++ b/qa/spec/specs/parallel_runner_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Specs::ParallelRunner do +RSpec.describe QA::Specs::ParallelRunner do include Helpers::StubENV before do diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 361588fa14f..8171cfcb3e6 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -2,7 +2,7 @@ require 'active_support/core_ext/hash' -describe QA::Specs::Runner do +RSpec.describe QA::Specs::Runner do shared_examples 'excludes orchestrated' do it 'excludes the orchestrated tag and includes default args' do expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS]) diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb index b5d5058ef49..18ccbf250cb 100644 --- a/qa/spec/support/repeater_spec.rb +++ b/qa/spec/support/repeater_spec.rb @@ -4,7 +4,7 @@ require 'logger' require 'timecop' require 'active_support/core_ext/integer/time' -describe QA::Support::Repeater do +RSpec.describe QA::Support::Repeater do before do logger = ::Logger.new $stdout logger.level = ::Logger::DEBUG diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb index ef1d53e6b65..6f052519516 100644 --- a/qa/spec/support/retrier_spec.rb +++ b/qa/spec/support/retrier_spec.rb @@ -3,7 +3,7 @@ require 'logger' require 'timecop' -describe QA::Support::Retrier do +RSpec.describe QA::Support::Retrier do before do logger = ::Logger.new $stdout logger.level = ::Logger::DEBUG diff --git a/qa/spec/support/wait_for_requests_spec.rb b/qa/spec/support/wait_for_requests_spec.rb index 79ee3eb5099..47c35addd9f 100644 --- a/qa/spec/support/wait_for_requests_spec.rb +++ b/qa/spec/support/wait_for_requests_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -describe QA::Support::WaitForRequests do +RSpec.describe QA::Support::WaitForRequests do describe '.wait_for_requests' do before do - allow(subject).to receive(:finished_all_axios_requests?).and_return(true) allow(subject).to receive(:finished_all_ajax_requests?).and_return(true) allow(subject).to receive(:finished_loading?).and_return(true) end diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb index 35f1e01289a..5b0c2c95d0d 100644 --- a/qa/spec/support/waiter_spec.rb +++ b/qa/spec/support/waiter_spec.rb @@ -2,7 +2,7 @@ require 'logger' -describe QA::Support::Waiter do +RSpec.describe QA::Support::Waiter do before do logger = ::Logger.new $stdout logger.level = ::Logger::DEBUG |