diff options
Diffstat (limited to 'qa')
108 files changed, 2156 insertions, 622 deletions
@@ -80,6 +80,7 @@ module QA autoload :CiVariable, 'qa/resource/ci_variable' autoload :Runner, 'qa/resource/runner' autoload :PersonalAccessToken, 'qa/resource/personal_access_token' + autoload :ProjectAccessToken, 'qa/resource/project_access_token' autoload :User, 'qa/resource/user' autoload :ProjectMilestone, 'qa/resource/project_milestone' autoload :GroupMilestone, 'qa/resource/group_milestone' @@ -96,6 +97,8 @@ module QA autoload :ProjectSnippet, 'qa/resource/project_snippet' autoload :Design, 'qa/resource/design' autoload :RegistryRepository, 'qa/resource/registry_repository' + autoload :Package, 'qa/resource/package' + autoload :PipelineSchedules, 'qa/resource/pipeline_schedules' module KubernetesCluster autoload :Base, 'qa/resource/kubernetes_cluster/base' @@ -317,6 +320,7 @@ module QA autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories' autoload :ProtectedTags, 'qa/page/project/settings/protected_tags' autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions' + autoload :AccessTokens, 'qa/page/project/settings/access_tokens' module Services autoload :Jira, 'qa/page/project/settings/services/jira' @@ -499,6 +503,8 @@ module QA autoload :Wiki, 'qa/page/component/wiki' autoload :WikiSidebar, 'qa/page/component/wiki_sidebar' autoload :WikiPageForm, 'qa/page/component/wiki_page_form' + autoload :AccessTokens, 'qa/page/component/access_tokens' + autoload :CommitModal, 'qa/page/component/commit_modal' module Issuable autoload :Common, 'qa/page/component/issuable/common' @@ -576,7 +582,9 @@ module QA autoload :LoopRunner, 'qa/specs/loop_runner' module Helpers + autoload :ContextSelector, 'qa/specs/helpers/context_selector' autoload :Quarantine, 'qa/specs/helpers/quarantine' + autoload :RSpec, 'qa/specs/helpers/rspec' end end diff --git a/qa/qa/flow/saml.rb b/qa/qa/flow/saml.rb index e8007978071..c414d648198 100644 --- a/qa/qa/flow/saml.rb +++ b/qa/qa/flow/saml.rb @@ -53,12 +53,10 @@ module QA end def run_saml_idp_service(group_name) - service = Service::DockerRun::SamlIdp.new(Runtime::Scenario.gitlab_address, group_name).tap do |runner| + Service::DockerRun::SamlIdp.new(Runtime::Scenario.gitlab_address, group_name).tap do |runner| runner.pull runner.register! end - - service end def remove_saml_idp_service(saml_idp_service) diff --git a/qa/qa/page/admin/settings/component/sign_up_restrictions.rb b/qa/qa/page/admin/settings/component/sign_up_restrictions.rb index 9909155641f..8b0d420d00a 100644 --- a/qa/qa/page/admin/settings/component/sign_up_restrictions.rb +++ b/qa/qa/page/admin/settings/component/sign_up_restrictions.rb @@ -6,19 +6,19 @@ module QA module Settings module Component class SignUpRestrictions < Page::Base - view 'app/views/admin/application_settings/_signup.html.haml' do + view 'app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue' do element :require_admin_approval_after_user_signup_checkbox element :signup_enabled_checkbox element :save_changes_button end def require_admin_approval_after_user_signup - check_element(:require_admin_approval_after_user_signup_checkbox) + click_element_coordinates(:require_admin_approval_after_user_signup_checkbox, visible: false) click_element(:save_changes_button) end def disable_signups - uncheck_element(:signup_enabled_checkbox) + click_element_coordinates(:signup_enabled_checkbox, visible: false) click_element(:save_changes_button) end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d1b556b58fb..289094268b6 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -132,7 +132,7 @@ module QA all(element_selector_css(name), **kwargs) end - def check_element(name) + def check_element(name, click_by_js = false) if find_element(name, visible: false).checked? QA::Runtime::Logger.debug("#{name} is already checked") @@ -140,7 +140,7 @@ module QA end retry_until(sleep_interval: 1) do - find_element(name, visible: false).click + click_checkbox_or_radio(name, click_by_js) checked = find_element(name, visible: false).checked? QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked") @@ -149,7 +149,7 @@ module QA end end - def uncheck_element(name) + def uncheck_element(name, click_by_js = false) unless find_element(name, visible: false).checked? QA::Runtime::Logger.debug("#{name} is already unchecked") @@ -157,7 +157,7 @@ module QA end retry_until(sleep_interval: 1) do - find_element(name, visible: false).click + click_checkbox_or_radio(name, click_by_js) unchecked = !find_element(name, visible: false).checked? QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked") @@ -166,13 +166,31 @@ module QA end end + # Method for selecting radios + def choose_element(name, click_by_js = false) + if find_element(name, visible: false).checked? + QA::Runtime::Logger.debug("#{name} is already selected") + + return + end + + retry_until(sleep_interval: 1) do + click_checkbox_or_radio(name, click_by_js) + selected = find_element(name, visible: false).checked? + + QA::Runtime::Logger.debug(selected ? "#{name} was selected" : "#{name} was not selected") + + selected + end + end + # Use this to simulate moving the pointer to an element's coordinate # and sending a click event. # This is a helpful workaround when there is a transparent element overlapping # the target element and so, normal `click_element` on target would raise # Selenium::WebDriver::Error::ElementClickInterceptedError - def click_element_coordinates(name) - page.driver.browser.action.move_to(find_element(name).native).click.perform + def click_element_coordinates(name, **kwargs) + page.driver.browser.action.move_to(find_element(name, **kwargs).native).click.perform end # replace with (..., page = self.class) @@ -403,6 +421,14 @@ module QA end end end + + private + + def click_checkbox_or_radio(name, click_by_js) + box = find_element(name, visible: false) + # Some checkboxes and radio buttons are hidden by their labels and cannot be clicked directly + click_by_js ? page.execute_script("arguments[0].click();", box) : box.click + end end end end diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb new file mode 100644 index 00000000000..d8e3d12b38b --- /dev/null +++ b/qa/qa/page/component/access_tokens.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module AccessTokens + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do + element :expiry_date_field + end + + base.view 'app/views/shared/access_tokens/_form.html.haml' do + element :access_token_name_field + element :create_token_button + end + + base.view 'app/views/shared/tokens/_scopes_form.html.haml' do + element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck + end + + base.view 'app/views/shared/access_tokens/_created_container.html.haml' do + element :created_access_token + end + + base.view 'app/views/shared/access_tokens/_table.html.haml' do + element :revoke_button + end + end + + def fill_token_name(name) + fill_element(:access_token_name_field, name) + end + + def check_api + check_element(:api_radio) + end + + def click_create_token_button + click_element(:create_token_button) + end + + def created_access_token + find_element(:created_access_token, wait: 30).value + end + + def fill_expiry_date(date) + date = date.to_s if date.is_a?(Date) + Date.strptime(date, '%Y-%m-%d') rescue ArgumentError raise "Expiry date must be in YYYY-MM-DD format" + + fill_element(:expiry_date_field, date) + end + + def has_token_row_for_name?(token_name) + page.has_css?('tr', text: token_name, wait: 1.0) + end + + def first_token_row_for_name(token_name) + page.find('tr', text: token_name, match: :first, wait: 1.0) + end + + def revoke_first_token_with_name(token_name) + within first_token_row_for_name(token_name) do + accept_confirm do + click_element(:revoke_button) + end + end + end + end + end + end +end diff --git a/qa/qa/page/component/commit_modal.rb b/qa/qa/page/component/commit_modal.rb new file mode 100644 index 00000000000..7192e8bafb5 --- /dev/null +++ b/qa/qa/page/component/commit_modal.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + class CommitModal < Page::Base + view 'app/assets/javascripts/projects/commit/components/form_modal.vue' do + element :submit_commit_button, required: true + end + end + end + end +end diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb index fbddb37f15e..9883ef22029 100644 --- a/qa/qa/page/component/invite_members_modal.rb +++ b/qa/qa/page/component/invite_members_modal.rb @@ -42,7 +42,7 @@ module QA within_element(:invite_members_modal_content) do fill_element :access_level_dropdown, with: access_level - fill_in 'Search for members to invite', with: username + fill_in 'Select members or type email addresses', with: username Support::WaitForRequests.wait_for_requests diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb index d7dfb0757bc..dbf4dc30116 100644 --- a/qa/qa/page/component/issue_board/show.rb +++ b/qa/qa/page/component/issue_board/show.rb @@ -11,6 +11,7 @@ module QA view 'app/assets/javascripts/boards/components/board_form.vue' do element :board_name_field + element :save_changes_button end view 'app/assets/javascripts/boards/components/board_list.vue' do @@ -23,10 +24,6 @@ module QA element :create_new_board_button end - view 'app/assets/javascripts/vue_shared/components/deprecated_modal.vue' do - element :save_changes_button - end - view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue' do element :labels_dropdown_content end @@ -35,7 +32,7 @@ module QA element :labels_edit_button end - view 'app/views/shared/boards/_show.html.haml' do + view 'app/assets/javascripts/boards/components/board_content.vue' do element :boards_list end diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index b98c429df8c..73f41e0aa51 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -25,10 +25,6 @@ module QA element :file_title_content end - base.view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do - element :file_content - end - base.view 'app/assets/javascripts/blob/components/blob_content.vue' do element :file_content end diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb index e24b1b67af1..bb22b7da003 100644 --- a/qa/qa/page/component/wiki_page_form.rb +++ b/qa/qa/page/component/wiki_page_form.rb @@ -9,12 +9,11 @@ module QA def self.included(base) super - base.view 'app/views/shared/wikis/_form.html.haml' do + base.view 'app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue' do element :wiki_title_textbox element :wiki_content_textarea element :wiki_message_textbox - element :save_changes_button - element :create_page_button + element :wiki_submit_button end base.view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do @@ -34,12 +33,8 @@ module QA fill_element(:wiki_message_textbox, message) end - def click_save_changes - click_element(:save_changes_button) - end - - def click_create_page - click_element(:create_page_button) + def click_submit + click_element(:wiki_submit_button) end def delete_page diff --git a/qa/qa/page/dashboard/snippet/index.rb b/qa/qa/page/dashboard/snippet/index.rb index 1f467fda9e1..8c4abfdf606 100644 --- a/qa/qa/page/dashboard/snippet/index.rb +++ b/qa/qa/page/dashboard/snippet/index.rb @@ -5,7 +5,7 @@ module QA module Dashboard module Snippet class Index < Page::Base - view 'app/views/layouts/header/_new_dropdown.haml' do + view 'app/views/layouts/header/_new_dropdown.html.haml' do element :new_menu_toggle element :global_new_snippet_link end diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb index ced8bd5c812..1ab849d10b1 100644 --- a/qa/qa/page/group/settings/general.rb +++ b/qa/qa/page/group/settings/general.rb @@ -9,6 +9,7 @@ module QA view 'app/views/groups/edit.html.haml' do element :permission_lfs_2fa_content + element :advanced_settings_content end view 'app/views/groups/settings/_permissions.html.haml' do @@ -40,6 +41,16 @@ module QA element :project_creation_level_dropdown end + view 'app/views/groups/settings/_advanced.html.haml' do + element :select_group_dropdown + element :transfer_group_button + end + + view 'app/helpers/dropdowns_helper.rb' do + element :dropdown_input_field + element :dropdown_list_content + end + def set_group_name(name) find_element(:group_name_field).send_keys([:command, 'a'], :backspace) find_element(:group_name_field).set name @@ -106,6 +117,19 @@ module QA click_element(:save_permissions_changes_button) end + + def transfer_group(target_group) + expand_content :advanced_settings_content + + click_element :select_group_dropdown + fill_element(:dropdown_input_field, target_group) + + within_element(:dropdown_list_content) do + click_on target_group + end + + click_element :transfer_group_button + end end end end diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index 8d0914bac4c..46b7bbeed84 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -8,8 +8,33 @@ module QA element :issuable_create_button, required: true end + view 'app/views/shared/form_elements/_description.html.haml' do + element :issuable_form_description + end + + view 'app/views/projects/merge_requests/show.html.haml' do + element :diffs_tab + end + + view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do + element :file_name_content + end + def create_merge_request - click_element :issuable_create_button, Page::MergeRequest::Show + click_element(:issuable_create_button, Page::MergeRequest::Show) + end + + def has_description?(description) + has_element?(:issuable_form_description, text: description) + end + + def click_diffs_tab + click_element(:diffs_tab) + click_element(:dismiss_popover_button) if has_element?(:dismiss_popover_button, wait: 1) + end + + def has_file?(file_name) + has_element?(:file_name_content, text: file_name) end end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 0b6a3085a3a..e1790deb3ec 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -49,6 +49,7 @@ module QA view 'app/views/projects/merge_requests/show.html.haml' do element :notes_tab + element :commits_tab element :diffs_tab end @@ -67,8 +68,11 @@ module QA element :edit_in_ide_button end - view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do + view 'app/assets/javascripts/diffs/components/diff_row.vue' do element :diff_comment_button + end + + view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do element :new_diff_line_link end @@ -104,6 +108,17 @@ module QA element :suggestion_button end + view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do + element :revert_button + element :cherry_pick_button + end + + view 'app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue' do + element :apply_suggestion_button + element :commit_message_textbox + element :commit_with_custom_message_button + end + def start_review click_element(:start_review_button) @@ -170,6 +185,10 @@ module QA wait_for_requests end + def click_commits_tab + click_element(:commits_tab) + end + def click_diffs_tab click_element(:diffs_tab) click_element(:dismiss_popover_button) if has_element?(:dismiss_popover_button, wait: 1) @@ -219,18 +238,12 @@ module QA end def mark_to_squash - # The squash checkbox is disabled on load - wait_until do - has_element?(:squash_checkbox) - end - # The squash checkbox is enabled via JS wait_until(reload: false) do - !find_element(:squash_checkbox).disabled? + !find_element(:squash_checkbox, visible: false).disabled? end - # TODO: Fix workaround for data-qa-selector failure - click_element(:squash_checkbox) + check_element(:squash_checkbox, true) end def merge! @@ -349,6 +362,12 @@ module QA click_element(:comment_now_button) end + def apply_suggestion_with_message(message) + click_element(:apply_suggestion_button) + fill_element(:commit_message_textbox, message) + click_element(:commit_with_custom_message_button) + end + def add_suggestion_to_batch all_elements(:add_suggestion_batch_button, minimum: 1).first.click end @@ -356,6 +375,16 @@ module QA def apply_suggestions_batch all_elements(:apply_suggestions_batch_button, minimum: 1).first.click end + + def cherry_pick! + click_element(:cherry_pick_button, Page::Component::CommitModal) + click_element(:submit_commit_button) + end + + def revert_change! + click_element(:revert_button, Page::Component::CommitModal) + click_element(:submit_commit_button) + end end end end diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb index caa8c0ceb40..75ba69bafa6 100644 --- a/qa/qa/page/profile/personal_access_tokens.rb +++ b/qa/qa/page/profile/personal_access_tokens.rb @@ -6,64 +6,7 @@ module QA module Page module Profile class PersonalAccessTokens < Page::Base - view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do - element :expiry_date_field - end - - view 'app/views/shared/access_tokens/_form.html.haml' do - element :access_token_name_field - element :create_token_button - end - - view 'app/views/shared/tokens/_scopes_form.html.haml' do - element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck - end - - view 'app/views/shared/access_tokens/_created_container.html.haml' do - element :created_access_token - end - view 'app/views/shared/access_tokens/_table.html.haml' do - element :revoke_button - end - - def fill_token_name(name) - fill_element(:access_token_name_field, name) - end - - def check_api - check_element(:api_radio) - end - - def click_create_token_button - click_element(:create_token_button) - end - - def created_access_token - find_element(:created_access_token, wait: 30).value - end - - def fill_expiry_date(date) - date = date.to_s if date.is_a?(Date) - Date.strptime(date, '%Y-%m-%d') rescue ArgumentError raise "Expiry date must be in YYYY-MM-DD format" - - fill_element(:expiry_date_field, date) - end - - def has_token_row_for_name?(token_name) - page.has_css?('tr', text: token_name, wait: 1.0) - end - - def first_token_row_for_name(token_name) - page.find('tr', text: token_name, match: :first, wait: 1.0) - end - - def revoke_first_token_with_name(token_name) - within first_token_row_for_name(token_name) do - accept_confirm do - click_element(:revoke_button) - end - end - end + include Page::Component::AccessTokens end end end diff --git a/qa/qa/page/project/commit/show.rb b/qa/qa/page/project/commit/show.rb index 8ece81f7088..f732eb6565e 100644 --- a/qa/qa/page/project/commit/show.rb +++ b/qa/qa/page/project/commit/show.rb @@ -6,10 +6,20 @@ module QA module Commit class Show < Page::Base view 'app/views/projects/commit/_commit_box.html.haml' do + element :commit_sha_content + end + + view 'app/assets/javascripts/projects/commit/components/commit_options_dropdown.vue' do element :options_button + element :cherry_pick_button element :email_patches element :plain_diff - element :commit_sha_content + end + + def cherry_pick_commit + click_element(:options_button) + click_element(:cherry_pick_button, Page::Component::CommitModal) + click_element(:submit_commit_button) end def select_email_patches diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb index bbdd4748f5c..5a08f6a3cbd 100644 --- a/qa/qa/page/project/fork/new.rb +++ b/qa/qa/page/project/fork/new.rb @@ -13,8 +13,18 @@ module QA element :fork_groups_list_search_field end - def choose_namespace(namespace = Runtime::Namespace.path) - click_element(:fork_namespace_button, name: namespace) + view 'app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue' do + element :fork_namespace_dropdown + element :fork_project_button + end + + def fork_project(namespace = Runtime::Namespace.path) + if has_element?(:fork_namespace_button, wait: 0) + click_element(:fork_namespace_button, name: namespace) + else + select_element(:fork_namespace_dropdown, namespace) + click_element(:fork_project_button) + end end def search_for_group(group_name) diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index 16c66ea5761..cb7323ac62d 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -13,8 +13,7 @@ module QA include SubMenus::Settings include SubMenus::Packages - view 'app/views/layouts/nav/sidebar/_project.html.haml' do - element :activity_link + view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do element :merge_requests_link element :snippets_link element :members_link @@ -24,6 +23,10 @@ module QA element :wiki_link end + view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do + element :sidebar_menu_item_link + end + def click_merge_requests within_sidebar do click_element(:merge_requests_link) @@ -38,7 +41,7 @@ module QA def click_activity within_sidebar do - click_element(:activity_link) + click_element(:sidebar_menu_item_link, menu_item: 'Activity') end end diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 0f5a7e8c801..3cb466abce9 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -9,8 +9,11 @@ module QA element :pipeline_url_link end - view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue' do + view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue' do element :pipeline_commit_status + end + + view 'app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue' do element :pipeline_retry_button end @@ -31,7 +34,7 @@ module QA end def wait_for_latest_pipeline_status - wait_until(max_duration: 30, reload: true, sleep_interval: 5) { has_pipeline? } + wait_until(max_duration: 90, reload: true, sleep_interval: 5) { has_pipeline? } wait_until(reload: false, max_duration: 360) do within_element_by_index(:pipeline_commit_status, 0) { yield } diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 994b1c02a3d..c5887b84be6 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -68,20 +68,30 @@ module QA end end - def has_child_pipeline? - has_element? :child_pipeline + def has_child_pipeline?(title: nil) + title ? find_child_pipeline_by_title(title) : has_element?(:child_pipeline) end def has_no_child_pipeline? - has_no_element? :child_pipeline + has_no_element?(:child_pipeline) end def click_job(job_name) click_element(:job_link, Project::Job::Show, text: job_name) end - def expand_child_pipeline - within_element(:child_pipeline) do + def child_pipelines + all_elements(:child_pipeline, minimum: 1) + end + + def find_child_pipeline_by_title(title) + child_pipelines.find { |pipeline| pipeline[:title].include?(title) } + end + + def expand_child_pipeline(title: nil) + child_pipeline = title ? find_child_pipeline_by_title(title) : child_pipelines.first + + within_element_by_index(:child_pipeline, child_pipelines.index(child_pipeline)) do click_element(:expand_pipeline_button) end end diff --git a/qa/qa/page/project/settings/access_tokens.rb b/qa/qa/page/project/settings/access_tokens.rb new file mode 100644 index 00000000000..d559ca4daaa --- /dev/null +++ b/qa/qa/page/project/settings/access_tokens.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'date' + +module QA + module Page + module Project + module Settings + class AccessTokens < Page::Base + include Page::Component::AccessTokens + end + end + end + end +end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 7a910233d12..7224fdae10e 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -42,3 +42,5 @@ module QA end end end + +QA::Page::Project::Settings::CICD.prepend_if_ee("QA::EE::Page::Project::Settings::CICD") diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb index dd676c86486..6f5c50eac52 100644 --- a/qa/qa/page/project/settings/integrations.rb +++ b/qa/qa/page/project/settings/integrations.rb @@ -5,9 +5,9 @@ module QA module Project module Settings class Integrations < QA::Page::Base - view 'app/views/shared/integrations/_index.html.haml' do - element :prometheus_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern - element :jira_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern + view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do + element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern + element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern end def click_on_prometheus_integration diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index fe5d629effe..0b4a12dbb2e 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -20,11 +20,11 @@ module QA end def click_save_changes - click_element :save_merge_request_changes_button + click_element(:save_merge_request_changes_button) end def enable_ff_only - click_element :merge_ff_radio_button + click_element(:merge_ff_radio_button) click_save_changes end diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index af4dbb08430..aa1ac216ae2 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -10,12 +10,9 @@ module QA element :coordinator_address, '%code#coordinator_address' # rubocop:disable QA/ElementWithPattern end - ## - # TODO, phase-out CSS classes added in Ruby helpers. - # view 'app/helpers/ci/runners_helper.rb' do # rubocop:disable Lint/InterpolationCheck - element :runner_status, 'runner-status-#{status}' # rubocop:disable QA/ElementWithPattern + element :runner_status_icon, 'qa_selector: "runner_status_#{status}_content"' # rubocop:disable QA/ElementWithPattern # rubocop:enable Lint/InterpolationCheck end @@ -28,7 +25,7 @@ module QA end def has_online_runner? - page.has_css?('.runner-status-online') + has_element?(:runner_status_online_content) end end end diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb index eaa3e90db78..0a56aaa758e 100644 --- a/qa/qa/page/project/settings/services/jira.rb +++ b/qa/qa/page/project/settings/services/jira.rb @@ -10,7 +10,13 @@ module QA element :service_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern - element :service_jira_issue_transition_id_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern + end + + view 'app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue' do + element :service_jira_issue_transition_enabled_checkbox + element :service_jira_issue_transition_automatic_true_radio, ':data-qa-selector="`service_jira_issue_transition_automatic_${issueTransitionOption.value}_radio`"' # rubocop:disable QA/ElementWithPattern + element :service_jira_issue_transition_automatic_false_radio, ':data-qa-selector="`service_jira_issue_transition_automatic_${issueTransitionOption.value}_radio`"' # rubocop:disable QA/ElementWithPattern + element :service_jira_issue_transition_id_field end view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do @@ -23,7 +29,10 @@ module QA set_jira_server_url(url) set_username(Runtime::Env.jira_admin_username) set_password(Runtime::Env.jira_admin_password) - set_transaction_ids('11,21,31,41') + + enable_transitions + use_custom_transitions + set_transition_ids('11,21,31,41') click_save_changes_button wait_until(reload: false) do @@ -45,12 +54,24 @@ module QA fill_element(:service_password_field, password) end - def set_transaction_ids(transaction_ids) - fill_element(:service_jira_issue_transition_id_field, transaction_ids) + def enable_transitions + check_element(:service_jira_issue_transition_enabled_checkbox, true) + end + + def use_automatic_transitions + choose_element(:service_jira_issue_transition_automatic_true_radio, true) + end + + def use_custom_transitions + choose_element(:service_jira_issue_transition_automatic_false_radio, true) + end + + def set_transition_ids(transition_ids) + fill_element(:service_jira_issue_transition_id_field, transition_ids) end def click_save_changes_button - click_element :save_changes_button + click_element(:save_changes_button) end end end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index d2c258b90b5..d8c6b3881bd 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -22,7 +22,7 @@ module QA element :file_tree_table end - view 'app/views/layouts/header/_new_dropdown.haml' do + view 'app/views/layouts/header/_new_dropdown.html.haml' do element :new_menu_toggle element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb index 9405ea97fff..398712c04d2 100644 --- a/qa/qa/page/project/sub_menus/ci_cd.rb +++ b/qa/qa/page/project/sub_menus/ci_cd.rb @@ -13,7 +13,7 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do + view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do element :link_pipelines end end diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb index 124faf0d346..384af3fb53e 100644 --- a/qa/qa/page/project/sub_menus/issues.rb +++ b/qa/qa/page/project/sub_menus/issues.rb @@ -13,7 +13,7 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do + view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do element :issue_boards_link element :issues_item element :labels_link diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb index 042994062c7..af716d1af0d 100644 --- a/qa/qa/page/project/sub_menus/operations.rb +++ b/qa/qa/page/project/sub_menus/operations.rb @@ -13,7 +13,7 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do + view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do element :operations_link element :operations_environments_link element :operations_metrics_link diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb index 4af640301b9..ecb3148b486 100644 --- a/qa/qa/page/project/sub_menus/project.rb +++ b/qa/qa/page/project/sub_menus/project.rb @@ -13,8 +13,8 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do - element :project_link + view 'app/views/shared/nav/_sidebar_menu.html.haml' do + element :sidebar_menu_link end end end @@ -22,7 +22,7 @@ module QA def click_project retry_on_exception do within_sidebar do - click_element(:project_link) + click_element(:sidebar_menu_link, menu_item: 'Project overview') end end end diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb index c78c7521b64..458f0cddab6 100644 --- a/qa/qa/page/project/sub_menus/repository.rb +++ b/qa/qa/page/project/sub_menus/repository.rb @@ -13,24 +13,26 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do - element :repository_link - element :branches_link - element :tags_link + view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do + element :sidebar_menu_item_link + end + + view 'app/views/shared/nav/_sidebar_menu.html.haml' do + element :sidebar_menu_link end end end def click_repository within_sidebar do - click_element(:repository_link) + click_element(:sidebar_menu_link, menu_item: 'Repository') end end def go_to_repository_branches hover_repository do within_submenu do - click_element(:branches_link) + click_element(:sidebar_menu_item_link, menu_item: 'Branches') end end end @@ -38,7 +40,7 @@ module QA def go_to_repository_tags hover_repository do within_submenu do - click_element(:tags_link) + click_element(:sidebar_menu_item_link, menu_item: 'Tags') end end end @@ -47,7 +49,7 @@ module QA def hover_repository within_sidebar do - find_element(:repository_link).hover + find_element(:sidebar_menu_link, menu_item: 'Repository').hover yield end diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index b5058bacccd..531c4686345 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -13,11 +13,12 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - view 'app/views/layouts/nav/sidebar/_project.html.haml' do + view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do element :settings_item element :general_settings_link element :integrations_settings_link element :operations_settings_link + element :access_tokens_settings_link end end end @@ -68,6 +69,14 @@ module QA end end + def go_to_access_token_settings + hover_settings do + within_submenu do + click_element :access_tokens_settings_link + end + end + end + private def hover_settings diff --git a/qa/qa/resource/events/base.rb b/qa/qa/resource/events/base.rb index 4c5f54825b3..d96f5a30f05 100644 --- a/qa/qa/resource/events/base.rb +++ b/qa/qa/resource/events/base.rb @@ -24,6 +24,18 @@ module QA "#{api_get_path}/events" end + def fetch_events + events_returned = nil + Support::Waiter.wait_until(max_duration: max_wait, raise_on_failure: raise_on_failure) do + events_returned = yield + events_returned.any? + end + + raise EventNotFoundError, "Timed out waiting for events" unless events_returned + + events_returned + end + def wait_for_event event_found = Support::Waiter.wait_until(max_duration: max_wait, raise_on_failure: raise_on_failure) do yield diff --git a/qa/qa/resource/events/project.rb b/qa/qa/resource/events/project.rb index 0348f2f05f5..2560e6b9e3b 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 push_events(commit_message) + QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait for and fetch push events"]) + fetch_events do + events(action: 'pushed').select { |event| event.dig(:push_data, :commit_title) == commit_message } + end + end + def wait_for_merge(title) QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_merge with title "#{title}"]) wait_for_event do diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb index d0aaaae6a11..106d1d5548a 100644 --- a/qa/qa/resource/fork.rb +++ b/qa/qa/resource/fork.rb @@ -3,6 +3,8 @@ module QA module Resource class Fork < Base + attr_accessor :namespace_path + attribute :name do upstream.name end @@ -31,6 +33,8 @@ module QA def fabricate! populate(:upstream, :user) + namespace_path ||= user.name + # Sign out as admin and sign is as the fork user Page::Main::Menu.perform(&:sign_out) Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -43,11 +47,11 @@ module QA Page::Project::Show.perform(&:fork_project) Page::Project::Fork::New.perform do |fork_new| - fork_new.choose_namespace(user.name) + fork_new.fork_project(namespace_path) end - Page::Layout::Banner.perform do |banner| - banner.has_notice?('The project was successfully forked.') + Page::Project::Show.perform do |project_page| + raise ResourceFabricationFailedError, "Forking failed!" unless project_page.forked_from?(upstream.name) end populate(:project) diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 135c3dea628..fb7236f9f4c 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -89,6 +89,19 @@ module QA raise ResourceUpdateFailedError, "Could not update require_two_factor_authentication to #{value}. Request returned (#{response.code}): `#{response}`." end end + + def change_repository_storage(new_storage) + post_body = { destination_storage_name: new_storage } + response = post Runtime::API::Request.new(api_client, "/groups/#{id}/repository_storage_moves").url, post_body + + unless response.code.between?(200, 300) + raise ResourceUpdateFailedError, "Could not change repository storage to #{new_storage}. Request returned (#{response.code}): `#{response}`." + end + + wait_until(sleep_interval: 1) { Runtime::API::RepositoryStorageMoves.has_status?(self, 'finished', new_storage) } + rescue Support::Repeater::RepeaterConditionExceededError + raise Runtime::API::RepositoryStorageMoves::RepositoryStorageMovesError, 'Timed out while waiting for the group repository storage move to finish' + end end end end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index fb450a61c9a..5a24bb32475 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -35,7 +35,7 @@ module QA attribute :target do Repository::ProjectPush.fabricate! do |resource| resource.project = project - resource.branch_name = project.default_branch + resource.branch_name = target_branch resource.new_branch = @target_new_branch resource.remote_branch = target_branch end @@ -62,6 +62,7 @@ module QA @labels = [] @file_name = "added_file-#{SecureRandom.hex(8)}.txt" @file_content = "File Added" + @target_branch = project.default_branch @target_new_branch = true @no_preparation = false @wait_for_merge = true diff --git a/qa/qa/resource/package.rb b/qa/qa/resource/package.rb new file mode 100644 index 00000000000..1009353a296 --- /dev/null +++ b/qa/qa/resource/package.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class Package < Base + attr_accessor :name + + attribute :project do + Project.fabricate_via_api! do |resource| + resource.name = 'project-with-package' + resource.description = 'Project with Package' + end + end + + attribute :id do + packages = project.packages + + return unless (this_package = packages&.find { |package| package[:name] == "#{project.path_with_namespace}/#{name}" }) # rubocop:disable Cop/AvoidReturnFromBlocks + + this_package[:id] + end + + def fabricate! + end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def remove_via_api! + packages = project.packages + + if packages && !packages.empty? + QA::Runtime::Logger.debug("Deleting package '#{name}' from '#{project.path_with_namespace}' via API") + super + end + end + + def api_delete_path + "/projects/#{project.id}/packages/#{id}" + end + + def api_get_path + "/projects/#{project.id}/packages" + end + end + end +end diff --git a/qa/qa/resource/pipeline_schedules.rb b/qa/qa/resource/pipeline_schedules.rb new file mode 100644 index 00000000000..3d51bcdbce5 --- /dev/null +++ b/qa/qa/resource/pipeline_schedules.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module QA + module Resource + class PipelineSchedules < Base + attribute :id + attribute :ref + attribute :description + + # Cron schedule form "* * * * *" + # String of integers in order of "minute hour day-of-month month day-of-week" + attribute :cron + + attribute :project do + Resource::Project.fabricate! do |project| + project.name = 'project-with-pipeline-schedule' + end + end + + def initialize + @cron = '0 * * * *' # default to schedule at the beginning of the hour + @description = 'QA test scheduling pipeline.' + @ref = project.default_branch + end + + def api_get_path + "/projects/#{project.id}/pipeline_schedules/#{id}" + end + + def api_post_path + "/projects/#{project.id}/pipeline_schedules" + end + + def api_post_body + { + description: description, + ref: ref, + cron: cron + } + end + + private + + def resource_web_url(resource) + resource = resource.has_key?(:owner) ? resource.fetch(:owner) : resource + super + end + end + end +end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 23e2ec07491..aaa882cffde 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -155,6 +155,10 @@ module QA "#{api_get_path}/registry/repositories" end + def api_packages_path + "#{api_get_path}/packages" + end + def api_commits_path "#{api_get_path}/repository/commits" end @@ -175,6 +179,10 @@ module QA "#{api_get_path}/pipelines" end + def api_pipeline_schedules_path + "#{api_get_path}/pipeline_schedules" + end + def api_put_path "/projects/#{id}" end @@ -262,7 +270,11 @@ module QA def registry_repositories response = get Runtime::API::Request.new(api_client, "#{api_registry_repositories_path}").url + parse_body(response) + end + def packages + response = get Runtime::API::Request.new(api_client, "#{api_packages_path}").url parse_body(response) end @@ -282,6 +294,10 @@ module QA parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url)) end + def pipeline_schedules + parse_body(get(Runtime::API::Request.new(api_client, api_pipeline_schedules_path).url)) + end + private def transform_api_resource(api_resource) diff --git a/qa/qa/resource/project_access_token.rb b/qa/qa/resource/project_access_token.rb new file mode 100644 index 00000000000..f5cd8798f19 --- /dev/null +++ b/qa/qa/resource/project_access_token.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'date' + +module QA + module Resource + class ProjectAccessToken < Base + attr_writer :name + + attribute :id + attribute :project do + Project.fabricate! + end + attribute :token do + Page::Project::Settings::AccessTokens.perform(&:created_access_token) + end + + def fabricate_via_api! + super + end + + def api_get_path + "/projects/#{project.api_resource[:id]}/access_tokens" + end + + def api_post_path + api_get_path + end + + def name + @name || 'api-project-access-token' + end + + def api_post_body + { + name: name, + scopes: ["api"] + } + end + + def api_delete_path + "projects/#{project.api_resource[:id]}/access_tokens/#{id}" + end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def revoke_via_ui! + Page::Project::Settings::AccessTokens.perform do |tokens_page| + tokens_page.revoke_first_token_with_name(name) + end + end + + def fabricate! + Flow::Login.sign_in_unless_signed_in + + project.visit! + + Page::Project::Menu.perform(&:go_to_access_token_settings) + + Page::Project::Settings::AccessTokens.perform do |token_page| + token_page.fill_token_name(name || 'api-project-access-token') + token_page.check_api + # Expire in 2 days just in case the token is created just before midnight + token_page.fill_expiry_date(Time.now.utc.to_date + 2) + token_page.click_create_token_button + end + end + end + end +end diff --git a/qa/qa/resource/registry_repository.rb b/qa/qa/resource/registry_repository.rb index 1e43d93a784..3de409232dd 100644 --- a/qa/qa/resource/registry_repository.rb +++ b/qa/qa/resource/registry_repository.rb @@ -6,7 +6,7 @@ module QA module Resource class RegistryRepository < Base attr_accessor :name, - :repository_id + :tag_name attribute :project do Project.fabricate_via_api! do |resource| @@ -15,9 +15,17 @@ module QA end end + attribute :id do + registry_repositories = project.registry_repositories + + return unless (this_registry_repository = registry_repositories&.find { |registry_repository| registry_repository[:path] == name }) # rubocop:disable Cop/AvoidReturnFromBlocks + + this_registry_repository[:id] + end + def initialize @name = project.path_with_namespace - @repository_id = nil + @tag_name = 'master' end def fabricate! @@ -31,23 +39,57 @@ module QA def remove_via_api! registry_repositories = project.registry_repositories - if registry_repositories && !registry_repositories.empty? - this_registry_repository = registry_repositories.find { |registry_repository| registry_repository[:path] == name } - - @repository_id = this_registry_repository[:id] + if registry_repositories && !registry_repositories.empty? QA::Runtime::Logger.debug("Deleting registry '#{name}'") super end end def api_delete_path - "/projects/#{project.id}/registry/repositories/#{@repository_id}" + "/projects/#{project.id}/registry/repositories/#{id}" + end + + def api_delete_tag_path + "/projects/#{project.id}/registry/repositories/#{id}/tags/#{tag_name}" end def api_get_path "/projects/#{project.id}/registry/repositories" end + + def api_get_tags_path + "/projects/#{project.id}/registry/repositories/#{id}/tags" + end + + def has_tag?(tag_name) + response = get Runtime::API::Request.new(api_client, api_get_tags_path).url + + raise ResourceNotFoundError, "Request returned (#{response.code}): `#{response}`." if response.code == HTTP_STATUS_NOT_FOUND + + tag_list = parse_body(response) + tag_list.any? { |tag| tag[:name] == tag_name } + end + + def has_no_tag?(tag_name) + response = get Runtime::API::Request.new(api_client, api_get_tags_path).url + + raise ResourceNotFoundError, "Request returned (#{response.code}): `#{response}`." if response.code == HTTP_STATUS_NOT_FOUND + + tag_list = parse_body(response) + tag_list.none? { |tag| tag[:name] == tag_name } + end + + def delete_tag + QA::Runtime::Logger.debug("Deleting registry tag '#{tag_name}'") + + request = Runtime::API::Request.new(api_client, api_delete_tag_path) + response = delete(request.url) + + unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED, HTTP_STATUS_OK].include? response.code + raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`." + end + end end end end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index d1a310c7c43..d98b7d7c79d 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -118,6 +118,10 @@ module QA '/users' end + def api_block_path + "/users/#{id}/block" + end + def api_post_body { admin: admin, @@ -143,6 +147,14 @@ module QA end end + def block! + response = post(Runtime::API::Request.new(api_client, api_block_path).url, nil) + + unless response.code == HTTP_STATUS_CREATED + raise ResourceUpdateFailedError, "Failed to block user. Request returned (#{response.code}): `#{response}`." + end + end + private def ldap_post_body diff --git a/qa/qa/resource/wiki/group_page.rb b/qa/qa/resource/wiki/group_page.rb index 27150ecf6c7..83beaf097ca 100644 --- a/qa/qa/resource/wiki/group_page.rb +++ b/qa/qa/resource/wiki/group_page.rb @@ -16,6 +16,14 @@ module QA end end + attribute :repository_http_location do + EE::Page::Group::Wiki::Show.perform do |show| + show.click_clone_repository + show.choose_repository_clone_http + show.repository_location + end + end + def initialize @title = 'Home' @content = 'This wiki page is created via API' @@ -42,6 +50,20 @@ module QA title: title } end + + def api_list_wiki_pages_path + "/groups/#{group.id}/wikis" + end + + def has_page_content?(page_title, page_content) + response = get Runtime::API::Request.new(api_client, "#{api_list_wiki_pages_path}?with_content=1").url + + unless response.code == HTTP_STATUS_OK + raise ResourceQueryError, "Could not get a list of all wiki pages for a given group. Request returned (#{response.code}): `#{response}`." + end + + parse_body(response).any? { |page| page[:title] == page_title && page[:content] == page_content } + end end end end diff --git a/qa/qa/runtime/api/repository_storage_moves.rb b/qa/qa/runtime/api/repository_storage_moves.rb index 5630a9c02c5..d1d44bd1ab5 100644 --- a/qa/qa/runtime/api/repository_storage_moves.rb +++ b/qa/qa/runtime/api/repository_storage_moves.rb @@ -33,6 +33,8 @@ module QA def resource_equals?(resource, move) if resource.class.name.include?('Snippet') move[:snippet][:id] == resource.id + elsif resource.class.name.include?('Group') + move[:group][:id] == resource.id else move[:project][:path_with_namespace] == resource.path_with_namespace end diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb index b58be354103..28bae541cb8 100644 --- a/qa/qa/runtime/api/request.rb +++ b/qa/qa/runtime/api/request.rb @@ -4,7 +4,7 @@ module QA module Runtime module API class Request - API_VERSION = 'v4'.freeze + API_VERSION = 'v4' def initialize(api_client, path, **query_string) query_string[:private_token] ||= api_client.personal_access_token unless query_string[:oauth_access_token] diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 7aa45204513..e4b92dc2e0d 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -2,6 +2,7 @@ require 'gitlab/qa' require 'uri' +require 'active_support/core_ext/object/blank' module QA module Runtime @@ -24,48 +25,6 @@ module QA SUPPORTED_FEATURES end - def context_matches?(*options) - return false unless Runtime::Scenario.attributes[:gitlab_address] - - opts = {} - opts[:domain] = '.+' - opts[:tld] = '.com' - - uri = URI(Runtime::Scenario.gitlab_address) - - options.each do |option| - opts[:domain] = 'gitlab' if option == :production - - if option.is_a?(Hash) && !option[:pipeline].nil? && !ci_project_name.nil? - return pipeline_matches?(option[:pipeline]) - - elsif option.is_a?(Hash) && !option[:subdomain].nil? - opts.merge!(option) - - opts[:subdomain] = case option[:subdomain] - when Array - "(#{option[:subdomain].join("|")})." - when Regexp - option[:subdomain] - else - "(#{option[:subdomain]})." - end - end - end - - uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/) - end - - alias_method :dot_com?, :context_matches? - - def pipeline_matches?(pipeline_to_run_in) - Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name) } - end - - def pipeline_from_project_name - ci_project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : ci_project_name - end - def additional_repository_storage ENV['QA_ADDITIONAL_REPOSITORY_STORAGE'] end @@ -82,6 +41,10 @@ module QA ENV['CI_JOB_URL'] end + def ci_job_name + ENV['CI_JOB_NAME'] + end + def ci_project_name ENV['CI_PROJECT_NAME'] end @@ -181,6 +144,10 @@ module QA ENV['GITLAB_PASSWORD'] end + def initial_root_password + ENV['GITLAB_INITIAL_ROOT_PASSWORD'] + end + def github_username ENV['GITHUB_USERNAME'] end diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index c50fcc25304..a836206034d 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -18,7 +18,7 @@ module QA end def default_password - '5iveL!fe' + Runtime::Env.initial_root_password || '5iveL!fe' end def username diff --git a/qa/qa/scenario/test/integration/object_storage.rb b/qa/qa/scenario/test/integration/object_storage.rb deleted file mode 100644 index 2e028bbb5c6..00000000000 --- a/qa/qa/scenario/test/integration/object_storage.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module QA - module Scenario - module Test - module Integration - class ObjectStorage < Test::Instance::All - tags :object_storage - end - end - end - end -end diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index 119013175c0..5adc52680f0 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -180,10 +180,10 @@ module QA wait_for_reliable_connection end - def verify_storage_move(source_storage, destination_storage) - return if QA::Runtime::Env.dot_com? + def verify_storage_move(source_storage, destination_storage, repo_type: :project) + return if Specs::Helpers::ContextSelector.dot_com? - repo_path = verify_storage_move_from_gitaly(source_storage[:name]) + repo_path = verify_storage_move_from_gitaly(source_storage[:name], repo_type: repo_type) destination_storage[:type] == :praefect ? verify_storage_move_to_praefect(repo_path, destination_storage[:name]) : verify_storage_move_to_gitaly(repo_path, destination_storage[:name]) end @@ -225,6 +225,10 @@ module QA ) end + def health_check_failure_message?(msg) + ['error when pinging healthcheck', 'failed checking node health'].include?(msg) + end + def wait_for_no_praefect_storage_error # If a healthcheck error was the last message to be logged, we'll keep seeing that message even if it's no longer a problem # That is, there's no message shown in the Praefect logs when the healthcheck succeeds @@ -241,7 +245,7 @@ module QA QA::Runtime::Logger.debug(line.chomp) log = JSON.parse(line) - break true if log['msg'] != 'error when pinging healthcheck' + break true unless health_check_failure_message?(log['msg']) rescue JSON::ParserError # Ignore lines that can't be parsed as JSON end @@ -302,7 +306,7 @@ module QA QA::Runtime::Logger.debug(line.chomp) log = JSON.parse(line) - log['msg'] == 'error when pinging healthcheck' && log['storage'] == node + health_check_failure_message?(log['msg']) && log['storage'] == node rescue JSON::ParserError # Ignore lines that can't be parsed as JSON end @@ -404,13 +408,13 @@ module QA Service::Shellout.sql_to_docker_exec_cmd(sql, 'postgres', 'SQL_PASSWORD', 'praefect_production', 'postgres.test', @postgres) end - def verify_storage_move_from_gitaly(storage) + def verify_storage_move_from_gitaly(storage, repo_type: :project) wait_until_shell_command("docker exec #{@gitlab} bash -c 'tail -n 50 /var/log/gitlab/gitaly/current'") do |line| log = JSON.parse(line) if (log['grpc.method'] == 'RenameRepository' || log['grpc.method'] == 'RemoveRepository') && log['grpc.request.repoStorage'] == storage && - !log['grpc.request.repoPath'].include?('wiki') + repo_type(log['grpc.request.repoPath']) == repo_type break log['grpc.request.repoPath'] end rescue JSON::ParserError @@ -444,6 +448,17 @@ module QA yield JSON.parse(line) end end + + def repo_type(repo_path) + return :snippet if repo_path.start_with?('@snippets') + return :design if repo_path.end_with?('.design.git') + + if repo_path.end_with?('.wiki.git') + return repo_path.start_with?('@groups') ? :group_wiki : :wiki + end + + :project + end end end end diff --git a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb new file mode 100644 index 00000000000..6024c8658d5 --- /dev/null +++ b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage' do + describe 'Project access token' do + before(:all) do + @project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! + @user_api_client = Runtime::API::Client.new(:gitlab, personal_access_token: @project_access_token.token) + end + + context 'for the same project' do + it 'can be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1734' do + expect do + Resource::File.fabricate_via_api! do |file| + file.api_client = @user_api_client + file.project = @project_access_token.project + file.branch = 'new_branch' + file.commit_message = 'Add new file' + file.name = "text-#{SecureRandom.hex(8)}.txt" + file.content = 'New file' + end + end.not_to raise_error + end + + it 'can be used to commit via the API', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1735' do + expect do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.api_client = @user_api_client + commit.project = @project_access_token.project + commit.branch = 'new_branch' + commit.start_branch = @project_access_token.project.default_branch + commit.commit_message = 'Add new file' + commit.add_files([ + { file_path: "text-#{SecureRandom.hex(8)}.txt", content: 'new file' } + ]) + end + end.not_to raise_error + end + end + + context 'for a different project' do + before(:all) do + @different_project = Resource::Project.fabricate! + end + + it 'cannot be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1736' do + expect do + Resource::File.fabricate_via_api! do |file| + file.api_client = @user_api_client + file.project = @different_project + file.branch = 'new_branch' + file.commit_message = 'Add new file' + file.name = "text-#{SecureRandom.hex(8)}.txt" + file.content = 'New file' + end + end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden/) + end + + it 'cannot be used to commit via the API', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1737' do + expect do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.api_client = @user_api_client + commit.project = @different_project + commit.branch = 'new_branch' + commit.start_branch = @different_project.default_branch + commit.commit_message = 'Add new file' + commit.add_files([ + { file_path: "text-#{SecureRandom.hex(8)}.txt", content: 'new file' } + ]) + end + end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden - You are not allowed to push into this branch/) + end + + after(:all) do + @different_project.remove_via_api! + end + end + + after(:all) do + @project_access_token.remove_via_api! + @project_access_token.project.remove_via_api! + end + end + end +end diff --git a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb index b7f71ad5bcd..a069b94f4da 100644 --- a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb +++ b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb @@ -12,7 +12,9 @@ module QA @user_api_client = Runtime::API::Client.new(:gitlab, user: @user) - @group = Resource::Group.fabricate_via_api! + @group = QA::Resource::Group.fabricate_via_api! do |group| + group.path = "group-to-test-access-termination-#{SecureRandom.hex(8)}" + end @group.sandbox.add_member(@user) @@ -73,11 +75,7 @@ module QA after(:all) do @user.remove_via_api! @project.remove_via_api! - begin - @group.remove_via_api! - rescue Resource::ApiFabricator::ResourceNotDeletedError - # It is ok if the group is already marked for deletion by another test - end + @group.remove_via_api! end end end diff --git a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb index 631056ed52e..176f1139a7a 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb @@ -9,7 +9,7 @@ module QA it 'confirms a `finished` status after moving project repository storage' do expect(project).to have_file('README.md') expect { project.change_repository_storage(destination_storage[:name]) }.not_to raise_error - expect { praefect_manager.verify_storage_move(source_storage, destination_storage) }.not_to raise_error + expect { praefect_manager.verify_storage_move(source_storage, destination_storage, repo_type: :project) }.not_to raise_error Resource::Repository::ProjectPush.fabricate! do |push| push.project = project @@ -45,7 +45,7 @@ module QA # Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect # scenario with other tests that aren't considered orchestrated. # It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage - context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/974', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do + context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1755', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } } let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } } let(:project) do diff --git a/qa/qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb b/qa/qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb new file mode 100644 index 00000000000..c06e3b9f162 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'PostReceive idempotent' do + # Tests that a push does not result in multiple changes from repeated PostReceive executions. + # One of the consequences would be duplicate push events + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'push-postreceive-idempotent' + project.initialize_with_readme = true + end + end + + after do + project&.remove_via_api! + end + + it 'pushes and creates a single push event three times', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1744' do + verify_single_event_per_push(repeat: 3) + end + + it 'repeatedly pushes and creates a single push event several times', :transient, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1744' do + verify_single_event_per_push(repeat: Runtime::Env.transient_trials) do |i| + QA::Runtime::Logger.info("Transient bug test action - Trial #{i}") + end + end + + def verify_single_event_per_push(repeat:) + repeat.times do |i| + yield i if block_given? + + commit_message = "test post-receive idempotency #{SecureRandom.hex(8)}" + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.new_branch = false + push.commit_message = commit_message + end + + events = project.push_events(commit_message) + + aggregate_failures do + expect(events.size).to eq(1), "An unexpected number of push events was created" + expect(events.first.dig(:push_data, :commit_title)).to eq(commit_message) + end + end + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/snippet/snippet_repository_storage_move_spec.rb b/qa/qa/specs/features/api/3_create/snippet/snippet_repository_storage_move_spec.rb index 4872acd1004..7b82a872fc0 100644 --- a/qa/qa/specs/features/api/3_create/snippet/snippet_repository_storage_move_spec.rb +++ b/qa/qa/specs/features/api/3_create/snippet/snippet_repository_storage_move_spec.rb @@ -24,7 +24,7 @@ module QA it 'moves snippet repository from one Gitaly storage to another', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1700' do expect(snippet).to have_file('original_file') expect { snippet.change_repository_storage(destination_storage[:name]) }.not_to raise_error - expect { praefect_manager.verify_storage_move(source_storage, destination_storage) }.not_to raise_error + expect { praefect_manager.verify_storage_move(source_storage, destination_storage, repo_type: :snippet) }.not_to raise_error # verifies you can push commits to the moved snippet Resource::Repository::Push.fabricate! do |push| diff --git a/qa/qa/specs/features/api/4_verify/.gitkeep b/qa/qa/specs/features/api/4_verify/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/qa/qa/specs/features/api/4_verify/.gitkeep +++ /dev/null diff --git a/qa/qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb b/qa/qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb new file mode 100644 index 00000000000..ecca0f94604 --- /dev/null +++ b/qa/qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify', :requires_admin do + describe 'When user is blocked' do + let!(:admin_api_client) { Runtime::API::Client.as_admin } + let!(:user_api_client) { Runtime::API::Client.new(:gitlab, user: user) } + + let(:user) do + Resource::User.fabricate_via_api! do |resource| + resource.api_client = admin_api_client + end + end + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-canceled-schedule' + end + end + + before do + project.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + + Resource::PipelineSchedules.fabricate_via_api! do |schedule| + schedule.api_client = user_api_client + schedule.project = project + end + + Support::Waiter.wait_until { !pipeline_schedule[:id].nil? && pipeline_schedule[:active] == true } + end + + after do + user.remove_via_api! + project.remove_via_api! + end + + it 'pipeline schedule is canceled', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1730' do + user.block! + + expect(pipeline_schedule[:active]).not_to be_truthy, "Expected schedule active state to be false - active state #{pipeline_schedule[:active]}" + end + + private + + def pipeline_schedule + project.pipeline_schedules.first + end + end + end +end diff --git a/qa/qa/specs/features/api/5_package/.gitkeep b/qa/qa/specs/features/api/5_package/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/qa/qa/specs/features/api/5_package/.gitkeep +++ /dev/null diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb new file mode 100644 index 00000000000..57b059ffc02 --- /dev/null +++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'airborne' + +module QA + RSpec.describe 'Package', only: { subdomain: :staging } do + include Support::Api + + describe 'Container Registry' do + let(:api_client) { Runtime::API::Client.new(:gitlab) } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-registry-api' + project.template_name = 'express' + end + end + + let(:registry) do + Resource::RegistryRepository.new.tap do |repository| + repository.name = "#{project.path_with_namespace}" + repository.project = project + repository.tag_name = 'master' + end + end + + let(:gitlab_ci_yaml) do + <<~YAML + stages: + - build + - test + + build: + image: docker:19.03.12 + stage: build + services: + - docker:19.03.12-dind + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + - docker pull $IMAGE_TAG + + test: + image: dwdraju/alpine-curl-jq:latest + stage: test + variables: + MEDIA_TYPE: 'application/vnd.docker.distribution.manifest.v2+json' + before_script: + - token=$(curl -u "$CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD" "https://$CI_SERVER_HOST/jwt/auth?service=container_registry&scope=repository:$CI_PROJECT_PATH:pull,push,delete" | jq -r '.token') + script: + - 'digest=$(curl -L -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/master" | jq -r ".layers[0].digest")' + - 'curl -L -X DELETE -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/blobs/$digest"' + - 'curl -L --head -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/blobs/$digest"' + - 'digest=$(curl -L -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/master" | jq -r ".config.digest")' + - 'curl -L -X DELETE -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/$digest"' + - 'curl -L --head -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/$digest"' + + YAML + end + + after do + registry&.remove_via_api! + end + + it 'pushes, pulls image to the registry and deletes image blob, manifest and tag', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1738' 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: gitlab_ci_yaml + }]) + end + + Support::Waiter.wait_until(max_duration: 10) { pipeline_is_triggered? } + + Support::Retrier.retry_until(max_duration: 260, sleep_interval: 5) do + latest_pipeline_succeed? + end + + expect(job_log).to have_content '404 Not Found' + + expect(registry).to have_tag('master') + + registry.delete_tag + + expect(registry).not_to have_tag('master') + end + + private + + def pipeline_is_triggered? + !project.pipelines.empty? + end + + def latest_pipeline_succeed? + latest_pipeline = project.pipelines.first + latest_pipeline[:status] == 'success' + end + + def job_log + pipeline = project.pipelines.first + pipeline_id = pipeline[:id] + + jobs = get Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipelines/#{pipeline_id}/jobs").url + test_job = parse_body(jobs).first + test_job_id = test_job[:id] + + log = get Runtime::API::Request.new(api_client, "/projects/#{project.id}/jobs/#{test_job_id}/trace").url + QA::Runtime::Logger.debug(" \n\n ------- Test job log: ------- \n\n #{log} \n -------") + + log + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb new file mode 100644 index 00000000000..d6bcd134a03 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage' do + describe 'Subgroup transfer' do + let(:source_group) do + Resource::Group.fabricate_via_api! do |group| + group.path = "source-group-for-transfer_#{SecureRandom.hex(8)}" + end + end + + let!(:target_group) do + Resource::Group.fabricate_via_api! do |group| + group.path = "target-group-for-transfer_#{SecureRandom.hex(8)}" + end + end + + let(:sub_group_for_transfer) do + Resource::Group.fabricate_via_api! do |group| + group.path = "subgroup-for-transfer_#{SecureRandom.hex(8)}" + group.sandbox = source_group + end + end + + before do + Flow::Login.sign_in + sub_group_for_transfer.visit! + end + + it 'transfers a subgroup to another group', + testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1724' do + Page::Group::Menu.perform(&:click_group_general_settings_item) + Page::Group::Settings::General.perform do |general| + general.transfer_group(target_group.path) + end + + expect(page).to have_text("Group '#{sub_group_for_transfer.path}' was successfully transferred.") + expect(page.driver.current_url).to include("#{target_group.path}/#{sub_group_for_transfer.path}") + end + + after do + source_group&.remove_via_api! + target_group&.remove_via_api! + sub_group_for_transfer&.remove_via_api! + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 4141060b6cb..5fbcd69817b 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -71,7 +71,7 @@ module QA Flow::Login.sign_in(as: user, skip_page_validation: true) - expect(page).to have_text("Invalid Login or password") + expect(page).to have_text("Invalid login or password") @recreated_user = Resource::User.fabricate_via_browser_ui! do |resource| resource.name = user.name diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index c8c483fc2aa..5072b6d48bf 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -29,7 +29,7 @@ module QA user.remove_via_api! end - it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/385' do + it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1762' do Flow::Login.sign_in(as: user) imported_project # import the project diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb new file mode 100644 index 00000000000..00d1b829150 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage' do + describe 'Project access tokens' do + let(:project_access_token) {QA::Resource::ProjectAccessToken.fabricate_via_browser_ui!} + + it 'can be created and revoked via the UI' do + expect(project_access_token.token).not_to be_nil + + project_access_token.revoke_via_ui! + expect(page).to have_text("Revoked project access token #{project_access_token.name}!") + end + + after do + project_access_token.project.remove_via_api! + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb index 7ec217cb47d..2a91c674a22 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/user/user_access_termination_spec.rb @@ -12,7 +12,9 @@ module QA end let!(:group) do - group = Resource::Group.fabricate_via_api! + group = QA::Resource::Group.fabricate_via_api! do |group| + group.path = "group-to-test-access-termination-#{SecureRandom.hex(8)}" + end group.sandbox.add_member(user) group end @@ -53,11 +55,7 @@ module QA after do user.remove_via_api! project.remove_via_api! - begin - group.remove_via_api! - rescue Resource::ApiFabricator::ResourceNotDeletedError - # It is ok if the group is already marked for deletion by another test - end + group.remove_via_api! end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index de6b24e8477..b2b37fcd424 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -48,7 +48,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/393' do + it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742' do Page::Project::Issue::Show.perform do |show| show.comment('See attached banana for scale', attachment: file_to_attach) diff --git a/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb new file mode 100644 index 00000000000..f2e4a320e04 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Plan', :transient do + describe 'Discussion comments transient bugs' do + let(:user1) do + Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) + end + + let(:my_first_reply) { 'This is my first reply' } + let(:my_second_reply) { "@#{Runtime::Env.gitlab_qa_username_1}" } + let(:my_third_reply) { "@#{Runtime::Env.gitlab_qa_username_1} This is my third reply" } + let(:my_fourth_reply) { '/close' } + + before do + Flow::Login.sign_in + end + + it 'comments with mention on a discussion in an issue', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1753' do + Runtime::Env.transient_trials.times do |i| + QA::Runtime::Logger.info("Transient bug test action - Trial #{i}") + + Resource::Issue.fabricate_via_api!.visit! + + Page::Project::Issue::Show.perform do |issue_page| + issue_page.select_all_activities_filter + issue_page.start_discussion('My first discussion') + issue_page.reply_to_discussion(1, my_first_reply) + + expect(issue_page).to have_comment(my_first_reply) + + issue_page.reply_to_discussion(1, "#{my_second_reply}\n") + + expect(issue_page).to have_comment(my_second_reply) + + issue_page.reply_to_discussion(1, my_third_reply) + + expect(issue_page).to have_comment(my_third_reply) + + issue_page.reply_to_discussion(1, my_fourth_reply) + + expect(issue_page).to have_system_note('closed') + end + end + end + end + end +end 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 index 43cf701acdd..6afc7549c59 100644 --- 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 @@ -23,7 +23,7 @@ module QA Flow::Login.sign_in end - it 'user archives a design', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/274' do + it 'user archives a design', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1761' do third_design.issue.visit! Page::Project::Issue::Show.perform do |issue| diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb index 9fbb0d69de1..dfdc9b7c9b4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb @@ -13,7 +13,7 @@ module QA Flow::Login.sign_in end - it 'user adds a design and modifies it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/273' do + it 'user adds a design and modifies it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1760' do design.issue.visit! Page::Project::Issue::Show.perform do |issue| diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb new file mode 100644 index 00000000000..16afa3be62a --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Cherry picking from a merge request' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project' + project.initialize_with_readme = true + end + end + + let(:feature_mr) do + Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.project = project + merge_request.target_branch = 'development' + merge_request.target_new_branch = true + end + end + + before do + Flow::Login.sign_in + end + + it 'creates a merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1616' do + feature_mr.visit! + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.merge! + merge_request.cherry_pick! + end + + Page::MergeRequest::New.perform(&:create_merge_request) + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.click_diffs_tab + expect(merge_request).to have_file(feature_mr.file_name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb new file mode 100644 index 00000000000..41746e84862 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Cherry picking a commit' do + let(:file_name) { "secret_file.md" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project' + project.initialize_with_readme = true + end + end + + let(:commit) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.branch = "development" + commit.start_branch = project.default_branch + commit.commit_message = 'Add new file' + commit.add_files([ + { file_path: file_name, content: 'pssst!' } + ]) + end + end + + before do + Flow::Login.sign_in + commit.visit! + end + + it 'creates a merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1752' do + Page::Project::Commit::Show.perform(&:cherry_pick_commit) + Page::MergeRequest::New.perform(&:create_merge_request) + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.click_diffs_tab + expect(merge_request).to have_file(file_name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 8d12042bd85..c2e148e19bd 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -3,13 +3,11 @@ module QA RSpec.describe 'Create' do describe 'Merge request creation from fork', :smoke do - let!(:merge_request) do - Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request| + it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1701' do + merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request| merge_request.fork_branch = 'feature-branch' end - end - it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1701' do Flow::Login.while_signed_in do merge_request.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb index 2ddc59acd5c..2b1ac75b9f5 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb @@ -3,82 +3,97 @@ module QA RSpec.describe 'Create', :runner do describe 'Merge requests' do - let(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'merge-when-pipeline-succeeds' - project.initialize_with_readme = true + shared_examples 'merge when pipeline succeeds' do |repeat: 1| + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'merge-when-pipeline-succeeds' + project.initialize_with_readme = true + end 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}"] + 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 - end - before 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: <<~EOF - test: - tags: ["runner-for-#{project.name}"] - script: sleep 5 - only: - - merge_requests - EOF - } - ] - ) + before 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: <<~EOF + test: + tags: ["runner-for-#{project.name}"] + script: sleep 10 + only: + - merge_requests + EOF + } + ] + ) + end + + Flow::Login.sign_in end - Flow::Login.sign_in - end + after do + runner&.remove_via_api! + project&.remove_via_api! + end - after do - runner&.remove_via_api! - project&.remove_via_api! - end + it 'merges after pipeline succeeds' do + repeat.times do |i| + QA::Runtime::Logger.info("Transient bug test - Trial #{i}") if repeat > 1 - it 'merges when pipeline succeeds', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1684' do - branch_name = "merge-request-test-#{SecureRandom.hex(8)}" + branch_name = "mr-test-#{SecureRandom.hex(6)}-#{i}" - # Create a branch that will be merged into the default branch - Resource::Repository::ProjectPush.fabricate! do |project_push| - project_push.project = project - project_push.new_branch = true - project_push.branch_name = branch_name - project_push.file_name = "file-#{SecureRandom.hex(8)}.txt" - end + # Create a branch that will be merged into the default branch + Resource::Repository::ProjectPush.fabricate! do |project_push| + project_push.project = project + project_push.new_branch = true + project_push.branch_name = branch_name + project_push.file_name = "#{branch_name}.txt" + end - # Create a merge request to merge the branch we just created - merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request| - merge_request.project = project - merge_request.source_branch = branch_name - merge_request.no_preparation = true - end + # Create a merge request to merge the branch we just created + merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.project = project + merge_request.source_branch = branch_name + merge_request.no_preparation = true + end - merge_request.visit! + merge_request.visit! - Page::MergeRequest::Show.perform do |mr| - mr.merge_when_pipeline_succeeds! + Page::MergeRequest::Show.perform do |mr| + mr.merge_when_pipeline_succeeds! - expect(mr.merge_request_status).to match(/to be merged automatically when the pipeline succeeds/) + Support::Waiter.wait_until(sleep_interval: 5) do + merge_request = merge_request.reload! + merge_request.state == 'merged' + end - Support::Waiter.wait_until(sleep_interval: 5) do - merge_request = merge_request.reload! - merge_request.state == 'merged' + aggregate_failures do + expect(merge_request.merge_when_pipeline_succeeds).to be_truthy + expect(mr.merged?).to be_truthy, "Expected content 'The changes were merged' but it did not appear." + end + end end - - expect(mr.merged?).to be_truthy, "Expected content 'The changes were merged' but it did not appear." end end + + context 'when merging once', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1684' do + it_behaves_like 'merge when pipeline succeeds' + end + + context 'when merging several times', :transient, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1684' do + it_behaves_like 'merge when pipeline succeeds', repeat: Runtime::Env.transient_trials + end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert_spec.rb new file mode 100644 index 00000000000..3574cdbe4ac --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Merged merge request' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'revert' + end + end + + let(:revertable_merge_request) do + Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.project = project + end + end + + before do + Flow::Login.sign_in + end + + it 'can be reverted', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1745' do + revertable_merge_request.visit! + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.merge! + merge_request.revert_change! + end + + Page::MergeRequest::New.perform(&:create_merge_request) + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.click_diffs_tab + expect(merge_request).to have_file(revertable_merge_request.file_name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/transient/apply_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb index eab2729af83..05e274de820 100644 --- a/qa/qa/specs/features/browser_ui/3_create/transient/apply_suggestion_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb @@ -2,25 +2,23 @@ module QA RSpec.describe 'Create' do - context 'Transient tests', :transient do + context 'Add batch suggestions to a Merge Request', :transient do let(:project) do Resource::Project.fabricate_via_api! do |project| - project.name = 'project-for-transient-test' + project.name = 'suggestions_project' end end - let(:code_for_merge) do - Pathname - .new(__dir__) - .join('../../../../../fixtures/metrics_dashboards/templating.yml') - end - let(:merge_request) do Resource::MergeRequest.fabricate_via_api! do |merge_request| merge_request.project = project - merge_request.title = 'Transient MR' - merge_request.description = 'detecting transient bugs' - merge_request.file_content = File.read(code_for_merge) + merge_request.title = 'Needs some suggestions' + merge_request.description = '... so please add them.' + merge_request.file_content = File.read( + Pathname + .new(__dir__) + .join('../../../../../../fixtures/metrics_dashboards/templating.yml') + ) end end @@ -30,6 +28,7 @@ module QA before do project.add_member(dev_user) + Flow::Login.sign_in(as: dev_user, skip_page_validation: true) merge_request.visit! @@ -38,7 +37,7 @@ module QA [4, 6, 10, 13].each do |line_number| Page::MergeRequest::Show.perform do |merge_request| - merge_request.add_suggestion_to_diff("This is the #{line_number} suggestion!", line_number) + merge_request.add_suggestion_to_diff("This is the suggestion for line number #{line_number}!", line_number) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb new file mode 100644 index 00000000000..ad12a3ec334 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Add suggestions to a Merge Request' do + let(:commit_message) { 'Applying suggested change for testing purposes.' } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'suggestions_project' + end + end + + let(:merge_request) do + Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.project = project + merge_request.title = 'Needs some suggestions' + merge_request.description = '... so please add them.' + merge_request.file_content = File.read( + Pathname + .new(__dir__) + .join('../../../../../../fixtures/metrics_dashboards/templating.yml') + ) + end + end + + let(:dev_user) do + Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) + end + + before do + project.add_member(dev_user) + + Flow::Login.sign_in(as: dev_user, skip_page_validation: true) + merge_request.visit! + + Page::MergeRequest::Show.perform do |merge_request| + merge_request.click_diffs_tab + merge_request.add_suggestion_to_diff('This is the suggestion for line number 4!', 4) + end + + Flow::Login.sign_in + merge_request.visit! + end + + it 'applies a single suggestion with a custom message' do + Page::MergeRequest::Show.perform do |merge_request| + merge_request.click_diffs_tab + merge_request.apply_suggestion_with_message(commit_message) + + expect(merge_request).to have_css('.badge-success', text: 'Applied') + + merge_request.click_commits_tab + + expect(merge_request).to have_content(commit_message) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb index 38c9216005f..ef3d45724db 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb @@ -27,7 +27,7 @@ module QA Page::Main::Menu.perform(&:sign_out_if_signed_in) end - it 'user pushes to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/386' do + it 'user pushes to the repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1763' do project = Resource::Project.fabricate_via_api! do |project| project.name = 'git-protocol-project' end 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 d44f5bc9e2e..70880011985 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 @@ -39,7 +39,7 @@ module QA 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 + it 'clones, pushes, and pulls a snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1748' do push = Resource::Repository::Push.fabricate! do |push| push.repository_http_uri = repository_uri_http push.file_name = new_file @@ -70,7 +70,7 @@ module QA snippet.remove_via_api! end - it 'clones, pushes, and pulls a snippet over SSH, deletes via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/825' do + it 'clones, pushes, and pulls a snippet over SSH, deletes via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1747' do push = Resource::Repository::Push.fabricate! do |push| push.repository_ssh_uri = repository_uri_ssh push.ssh_key = ssh_key diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb index ddbc98c4a1a..4f241325437 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :smoke do + RSpec.describe 'Create' do # convert back to a smoke test once proved to be stable describe 'Personal snippet creation' do it 'user creates a personal snippet', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1704' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb index 6044c87d24e..70959dd0200 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb @@ -27,7 +27,7 @@ module QA edit.set_message commit_message end - Page::Project::Wiki::Edit.perform(&:click_create_page) + Page::Project::Wiki::Edit.perform(&:click_submit) Page::Project::Wiki::Show.perform do |wiki| expect(wiki).to have_title new_wiki_title @@ -46,7 +46,7 @@ module QA edit.set_message commit_message end - Page::Project::Wiki::Edit.perform(&:click_create_page) + Page::Project::Wiki::Edit.perform(&:click_submit) Page::Project::Wiki::Show.perform do |wiki| expect(wiki).to have_title new_wiki_title diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb index 30a91c2e254..9a6d7d08e7b 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb @@ -25,7 +25,7 @@ module QA edit.set_message commit_message end - Page::Project::Wiki::Edit.perform(&:click_save_changes) + Page::Project::Wiki::Edit.perform(&:click_submit) Page::Project::Wiki::Show.perform do |wiki| expect(wiki).to have_title new_wiki_title 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 4f1d9ac1696..1a46322d283 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 @@ -20,7 +20,7 @@ module QA edit.set_message('changing the path of the home page') end - Page::Project::Wiki::Edit.perform(&:click_save_changes) + Page::Project::Wiki::Edit.perform(&:click_submit) Page::Project::Wiki::Show.perform do |wiki| expect(wiki).to have_directory('a') diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb index fcd8cb02870..39cbd0028c0 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb @@ -16,7 +16,7 @@ module QA add_ci_variable end - it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/395' do + it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1759' do Page::Project::Settings::CiVariables.perform do |ci_variable| expect(ci_variable).to have_text('VARIABLE_KEY') expect(ci_variable).not_to have_text('some_CI_variable') @@ -27,7 +27,7 @@ module QA end end - it 'user removes a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/394' do + it 'user removes a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1758' do Page::Project::Settings::CiVariables.perform do |ci_variable| ci_variable.click_edit_ci_variable ci_variable.click_ci_variable_delete_button diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb new file mode 100644 index 00000000000..5083b7b0859 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify', :requires_admin do + describe 'Include local config file paths with wildcard' do + let(:feature_flag) { :ci_wildcard_file_paths } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-pipeline' + end + end + + before do + Runtime::Feature.enable(feature_flag) + Flow::Login.sign_in + add_files_to_project + project.visit! + Flow::Pipeline.visit_latest_pipeline + end + + after do + Runtime::Feature.disable(feature_flag) + project.remove_via_api! + end + + it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1757' do + Page::Project::Pipeline::Show.perform do |pipeline| + aggregate_failures 'pipeline has all expected jobs' do + expect(pipeline).to have_job('build') + expect(pipeline).to have_job('test') + expect(pipeline).not_to have_job('deploy') + end + end + end + + private + + def add_files_to_project + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add CI and local files' + commit.add_files([build_config_file, test_config_file, non_detectable_file, main_ci_file]) + end + end + + def main_ci_file + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + include: 'configs/*.yml' + YAML + } + end + + def build_config_file + { + file_path: 'configs/builds.yml', + content: <<~YAML + build: + stage: build + script: echo build + YAML + } + end + + def test_config_file + { + file_path: 'configs/tests.yml', + content: <<~YAML + test: + stage: test + script: echo test + YAML + } + end + + def non_detectable_file + { + file_path: 'configs/not_included.yaml', # we only include `*.yml` not `*.yaml` + content: <<~YAML + deploy: + stage: deploy + script: echo deploy + YAML + } + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb index c5d73d2fd7d..01aada2d6dd 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb @@ -39,6 +39,7 @@ module QA needs: [test_blocked_pipeline] script: echo do not click me when: manual + allow_failure: false dummy_job: stage: deploy @@ -71,8 +72,9 @@ module QA it 'can still merge MR successfully', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/971' do Page::MergeRequest::Show.perform do |show| - show.wait_until(reload: false) { show.has_pipeline_status?('running') } - show.merge_immediately! + # waiting for manual action status shows status badge 'blocked' on pipelines page + show.wait_until(reload: false) { show.has_pipeline_status?('waiting for manual action') } + show.merge! expect(show).to be_merged end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb new file mode 100644 index 00000000000..d87fa0f5127 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'faker' + +module QA + RSpec.describe 'Verify', :runner do + describe 'Trigger matrix' do + let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-pipeline' + end + end + + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = executor + runner.tags = [executor] + end + end + + before do + Flow::Login.sign_in + add_ci_files + project.visit! + Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'succeeded') + end + + after do + runner.remove_via_api! + project.remove_via_api! + end + + it 'creates 2 trigger jobs and passes corresponding matrix variables', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1732' do + Page::Project::Pipeline::Show.perform do |parent_pipeline| + trigger_title1 = 'deploy: [ovh, monitoring]' + trigger_title2 = 'deploy: [ovh, app]' + + aggregate_failures 'Creates two child pipelines' do + expect(parent_pipeline).to have_child_pipeline(title: trigger_title1) + expect(parent_pipeline).to have_child_pipeline(title: trigger_title2) + end + + # Only check output of one of the child pipelines, should be sufficient + parent_pipeline.expand_child_pipeline(title: trigger_title1) + parent_pipeline.click_job('test_vars') + end + + Page::Project::Job::Show.perform do |show| + Support::Waiter.wait_until { show.successful? } + + aggregate_failures 'Job output has the correct variables' do + expect(show.output).to have_content('ovh') + expect(show.output).to have_content('monitoring') + end + end + end + + private + + def add_ci_files + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add parent and child pipelines CI files.' + commit.add_files( + [ + child_ci_file, + parent_ci_file + ] + ) + end + end + + def parent_ci_file + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + test: + stage: test + script: echo test + tags: [#{executor}] + + deploy: + stage: deploy + trigger: + include: child.yml + parallel: + matrix: + - PROVIDER: ovh + STACK: [monitoring, app] + + YAML + } + end + + def child_ci_file + { + file_path: 'child.yml', + content: <<~YAML + test_vars: + script: + - echo $PROVIDER + - echo $STACK + tags: [#{executor}] + YAML + } + 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 9ce87f353d0..916b809ebc1 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 @@ -15,7 +15,7 @@ module QA runner.remove_via_api! end - it 'user registers a new specific runner', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/392' do + it 'user registers a new specific runner', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1764' do Flow::Login.sign_in runner.project.visit! 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 5bfc88e45f2..d6d8729114d 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 @@ -33,7 +33,7 @@ module QA runner.remove_via_api! end - it 'creates an MR with code coverage statistics', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/807' do + it 'creates an MR with code coverage statistics', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1740' do runner.project.visit! configure_code_coverage(simplecov) merge_request.visit! @@ -42,7 +42,7 @@ module QA Support::Retrier.retry_until(max_attempts: 5, sleep_interval: 5) do mr_widget.has_pipeline_status?(/Pipeline #\d+ passed/) end - expect(mr_widget).to have_content('Coverage 66.67%') + expect(mr_widget).to have_content('Test coverage 66.67%') end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb index 6c1a0cea209..2489545782a 100644 --- a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb @@ -1,18 +1,25 @@ # frozen_string_literal: true +require 'securerandom' + module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'Composer Repository' do include Runtime::Fixtures - let(:package_name) { 'my_package' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'composer-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "my_package-#{SecureRandom.hex(4)}" + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -30,7 +37,7 @@ module QA let(:composer_json_file) do <<~EOF { - "name": "#{project.path_with_namespace}/#{package_name}", + "name": "#{project.path_with_namespace}/#{package.name}", "description": "Library XY", "type": "library", "license": "GPL-3.0-only", @@ -94,14 +101,15 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes a composer package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1088' do Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -109,7 +117,7 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures 'package deletion' do expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb index 7effefc4d73..a1e2eb1046c 100644 --- a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb @@ -5,14 +5,19 @@ module QA describe 'Conan Repository' do include Runtime::Fixtures - let(:package_name) { 'conantest' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'conan-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = 'conantest' + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -29,6 +34,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes, installs, and deletes a Conan package', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1077' do @@ -47,10 +53,10 @@ module QA stage: deploy script: - "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan" - - "conan new #{package_name}/0.1 -t" + - "conan new #{package.name}/0.1 -t" - "conan create . mycompany/stable" - - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package_name}/0.1@mycompany/stable --all --remote=gitlab" - - "conan install conantest/0.1@mycompany/stable --remote=gitlab" + - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package.name}/0.1@mycompany/stable --all --remote=gitlab" + - "conan install #{package.name}/0.1@mycompany/stable --remote=gitlab" tags: - "runner-for-#{project.name}" YAML @@ -71,15 +77,15 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb index 3e3addf9eeb..bbf8ea8c05e 100644 --- a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb @@ -3,14 +3,19 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'Generic Repository' do - let(:package_name) { 'my_package' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'generic-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "my_package" + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -90,14 +95,15 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'uploads a generic package, downloads and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1108' do Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -105,7 +111,7 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures 'package deletion' do expect(index).to have_content("Package deleted successfully") - expect(index).to have_no_package(package_name) + expect(index).to have_no_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb index b5b050a5dfe..4d4f981f021 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb @@ -23,6 +23,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = package_name + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -39,6 +46,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! end it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do 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 2f508d00790..7234edb46d3 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 @@ -31,6 +31,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = package_name + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" 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 97df8fedf87..49b42bd1ff6 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 @@ -5,8 +5,7 @@ module QA describe 'npm registry' do include Runtime::Fixtures - let(:registry_scope) { project.group.sandbox.path } - let(:package_name) { "@#{registry_scope}/#{project.name}" } + let!(:registry_scope) { project.group.sandbox.path } let(:auth_token) do unless Page::Main::Menu.perform(&:signed_in?) Flow::Login.sign_in @@ -15,12 +14,23 @@ module QA Resource::PersonalAccessToken.fabricate!.token end - let(:project) do + let!(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'npm-registry-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "@#{registry_scope}/#{project.name}" + package.project = project + end + end + + after do + package.remove_via_api! + end + it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do uri = URI.parse(Runtime::Scenario.gitlab_address) gitlab_host_with_port = "#{uri.host}:#{uri.port}" @@ -29,7 +39,7 @@ module QA file_path: 'package.json', content: <<~JSON { - "name": "#{package_name}", + "name": "#{package.name}", "version": "1.0.0", "description": "Example package for GitLab npm registry", "publishConfig": { @@ -56,20 +66,20 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) + expect(index).to have_package(package.name) - index.click_package(package_name) + index.click_package(package.name) end Page::Project::Packages::Show.perform do |show| - expect(show).to have_package_info(package_name, "1.0.0") + expect(show).to have_package_info(package.name, "1.0.0") show.click_delete end Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb index f143bc52095..c0d84d82e51 100644 --- a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb @@ -6,8 +6,6 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'NuGet Repository' do include Runtime::Fixtures - - let(:package_name) { "dotnetcore-#{SecureRandom.hex(8)}" } let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'nuget-package-project' @@ -15,6 +13,13 @@ module QA end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = "dotnetcore-#{SecureRandom.hex(8)}" + package.project = project + end + end + let(:another_project) do Resource::Project.fabricate_via_api! do |project| project.name = 'nuget-package-install-project' @@ -43,6 +48,7 @@ module QA after do runner.remove_via_api! another_runner.remove_via_api! + package.remove_via_api! end it 'publishes a nuget package at the project level, installs and deletes it at the group level', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1073' do @@ -66,7 +72,7 @@ module QA script: - dotnet restore -p:Configuration=Release - dotnet build -c Release - - dotnet pack -c Release -p:PackageID=#{package_name} + - dotnet pack -c Release -p:PackageID=#{package.name} - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text - dotnet nuget push "bin/Release/*.nupkg" --source gitlab only: @@ -127,7 +133,7 @@ module QA script: - dotnet nuget locals all --clear - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password $CI_JOB_TOKEN --store-password-in-clear-text - - "dotnet add otherdotnet.csproj package #{package_name} --version 1.0.0" + - "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0" only: - "#{another_project.default_branch}" tags: @@ -153,15 +159,15 @@ module QA Page::Group::Menu.perform(&:go_to_group_packages) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) Page::Project::Packages::Index.perform do |index| expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb index 396863b33c4..fb29af43da6 100644 --- a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb @@ -4,15 +4,19 @@ module QA RSpec.describe 'Package', :orchestrated, :packages do describe 'PyPI Repository' do include Runtime::Fixtures - - let(:package_name) { 'mypypipackage' } - let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'pypi-package-project' end end + let(:package) do + Resource::Package.new.tap do |package| + package.name = 'mypypipackage' + package.project = project + end + end + let!(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" @@ -87,6 +91,7 @@ module QA after do runner.remove_via_api! + package.remove_via_api! project&.remove_via_api! end @@ -94,8 +99,8 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) - index.click_package(package_name) + expect(index).to have_package(package.name) + index.click_package(package.name) end Page::Project::Packages::Show.perform(&:click_delete) @@ -103,13 +108,13 @@ module QA Page::Project::Packages::Index.perform do |index| aggregate_failures do expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + expect(index).not_to have_package(package.name) end end end context 'Geo', :orchestrated, :geo do - it 'replicates a published pypi package to the Geo secondary site', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1120' do + it 'replicates a published pypi package to the Geo secondary site', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1120', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/325556', type: :investigating } do QA::Runtime::Logger.debug('Visiting the secondary Geo site') QA::Flow::Login.while_signed_in(address: :geo_secondary) do @@ -127,8 +132,8 @@ module QA Page::Project::Menu.perform(&:click_packages_link) Page::Project::Packages::Index.perform do |index| - index.wait_for_package_replication(package_name) - expect(index).to have_package(package_name) + index.wait_for_package_replication(package.name) + expect(index).to have_package(package.name) end end end diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index 87bd2b76560..713b32de217 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Release' do describe 'Deploy key creation' do - it 'user adds a deploy key', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/390' do + it 'user adds a deploy key', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1765' do Flow::Login.sign_in key = Runtime::Key::RSA.new diff --git a/qa/qa/specs/helpers/context_selector.rb b/qa/qa/specs/helpers/context_selector.rb new file mode 100644 index 00000000000..4313f7c34dd --- /dev/null +++ b/qa/qa/specs/helpers/context_selector.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'rspec/core' + +module QA + module Specs + module Helpers + module ContextSelector + extend self + + def configure_rspec + ::RSpec.configure do |config| + config.before do |example| + if example.metadata.key?(:only) + skip('Test is not compatible with this environment or pipeline') unless ContextSelector.context_matches?(example.metadata[:only]) + elsif example.metadata.key?(:exclude) + skip('Test is excluded in this job') if ContextSelector.exclude?(example.metadata[:exclude]) + end + end + end + end + + def exclude?(*options) + return false unless Runtime::Env.ci_job_name.present? + + context_matches?(*options) + end + + def context_matches?(*options) + return false unless Runtime::Scenario.attributes[:gitlab_address] + + opts = {} + opts[:domain] = '.+' + opts[:tld] = '.com' + + uri = URI(Runtime::Scenario.gitlab_address) + + options.each do |option| + opts[:domain] = 'gitlab' if option == :production + + next unless option.is_a?(Hash) + + if option[:pipeline].present? && Runtime::Env.ci_project_name.present? + return pipeline_matches?(option[:pipeline]) + + elsif option[:job].present? + return job_matches?(option[:job]) + + elsif option[:subdomain].present? + opts.merge!(option) + + opts[:subdomain] = case option[:subdomain] + when Array + "(#{option[:subdomain].join("|")})." + when Regexp + option[:subdomain] + else + "(#{option[:subdomain]})." + end + end + end + + uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/) + end + + alias_method :dot_com?, :context_matches? + + def job_matches?(job_patterns) + Array(job_patterns).any? do |job| + pattern = job.is_a?(Regexp) ? job : Regexp.new(job) + pattern = Regexp.new(pattern.source, pattern.options | Regexp::IGNORECASE) + pattern =~ Runtime::Env.ci_job_name + end + end + + def pipeline_matches?(pipeline_to_run_in) + Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name(Runtime::Env.ci_project_name)) } + end + + def pipeline_from_project_name(project_name) + project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : project_name + end + end + end + end +end diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb index d365819057e..15b4ed8336b 100644 --- a/qa/qa/specs/helpers/quarantine.rb +++ b/qa/qa/specs/helpers/quarantine.rb @@ -6,22 +6,18 @@ module QA module Specs module Helpers module Quarantine - include RSpec::Core::Pending + include ::RSpec::Core::Pending extend self def configure_rspec - RSpec.configure do |config| + ::RSpec.configure do |config| config.before(:context, :quarantine) do Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class) end config.before do |example| Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example) - - if example.metadata.key?(:only) - skip('Test is not compatible with this environment or pipeline') unless Runtime::Env.context_matches?(example.metadata[:only]) - end end end end @@ -52,10 +48,10 @@ module QA if example.metadata.key?(:quarantine) quarantine_tag = example.metadata[:quarantine] - if quarantine_tag&.is_a?(Hash) && quarantine_tag&.key?(:only) + if quarantine_tag.is_a?(Hash) && quarantine_tag&.key?(:only) # If the :quarantine hash contains :only, we respect that. # For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging. - return unless Runtime::Env.context_matches?(quarantine_tag[:only]) + return unless ContextSelector.context_matches?(quarantine_tag[:only]) end skip(quarantine_message(quarantine_tag)) diff --git a/qa/qa/specs/helpers/rspec.rb b/qa/qa/specs/helpers/rspec.rb new file mode 100644 index 00000000000..f49e556b0d9 --- /dev/null +++ b/qa/qa/specs/helpers/rspec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rspec/core' + +module QA + module Specs + module Helpers + module RSpec + # We need a reporter for internal tests that's different from the reporter for + # external tests otherwise the results will be mixed up. We don't care about + # most reporting, but we do want to know if a test fails + class RaiseOnFailuresReporter < ::RSpec::Core::NullReporter + def self.example_failed(example) + raise example.exception + end + end + + # We use an example group wrapper to prevent the state of internal tests + # expanding into the global state + # See: https://github.com/rspec/rspec-core/issues/2603 + def describe_successfully(*args, &describe_body) + example_group = RSpec.describe(*args, &describe_body) + ran_successfully = example_group.run RaiseOnFailuresReporter + expect(ran_successfully).to eq true + example_group + end + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index 04eaa02bda5..ff690962db8 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -44,7 +44,7 @@ module QA tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? - tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Runtime::Env.dot_com? + tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Specs::Helpers::ContextSelector.dot_com? QA::Runtime::Env.supported_features.each_key do |key| tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index e183d711b30..f5299ed840d 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -52,19 +52,19 @@ module QA elements end - def check_element(name) + def check_element(name, click_by_js = nil) log("checking :#{name}") super end - def uncheck_element(name) + def uncheck_element(name, click_by_js = nil) log("unchecking :#{name}") super end - def click_element_coordinates(name) + def click_element_coordinates(name, **kwargs) log(%Q(clicking the coordinates of :#{name})) super @@ -81,7 +81,7 @@ module QA end def fill_element(name, content) - masked_content = name.to_s.include?('password') ? '*****' : content + masked_content = name.to_s.match?(/token|key|password/) ? '*****' : content log(%Q(filling :#{name} with "#{masked_content}")) diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 5a98721466f..8218ab428b0 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -341,56 +341,4 @@ RSpec.describe QA::Runtime::Env do end end end - - describe '.context_matches?' do - it 'returns true when url has .com' do - QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") - - expect(described_class.dot_com?).to be_truthy - end - - it 'returns false when url does not have .com' do - QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test") - - expect(described_class.dot_com?).to be_falsey - end - - context 'with arguments' do - it 'returns true when :subdomain is set' do - QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") - - expect(described_class.dot_com?(subdomain: :staging)).to be_truthy - end - - it 'matches multiple subdomains' do - QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") - - expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy - expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy - end - - it 'matches :production' do - QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/") - - expect(described_class.context_matches?(:production)).to be_truthy - end - - it 'doesnt match with mismatching switches' do - QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test') - - aggregate_failures do - expect(described_class.context_matches?(tld: '.net')).to be_falsey - expect(described_class.context_matches?(:production)).to be_falsey - expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey - expect(described_class.context_matches?(domain: 'example')).to be_falsey - end - end - end - - it 'returns false for mismatching' do - QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") - - expect(described_class.context_matches?(:production)).to be_falsey - end - end end diff --git a/qa/spec/scenario/test/integration/object_storage_spec.rb b/qa/spec/scenario/test/integration/object_storage_spec.rb deleted file mode 100644 index 235dd495687..00000000000 --- a/qa/spec/scenario/test/integration/object_storage_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe QA::Scenario::Test::Integration::ObjectStorage do - describe '#perform' do - it_behaves_like 'a QA scenario class' do - let(:tags) { [:object_storage] } - end - end -end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 16d86ef6ed2..631ebf65893 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -22,6 +22,7 @@ RSpec.configure do |config| config.include ::Matchers QA::Specs::Helpers::Quarantine.configure_rspec + QA::Specs::Helpers::ContextSelector.configure_rspec config.before do |example| QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n") diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb new file mode 100644 index 00000000000..16b6c6601b1 --- /dev/null +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -0,0 +1,294 @@ +# frozen_string_literal: true + +require 'rspec/core/sandbox' + +RSpec.configure do |c| + c.around do |ex| + RSpec::Core::Sandbox.sandboxed do |config| + # If there is an example-within-an-example, we want to make sure the inner example + # does not get a reference to the outer example (the real spec) if it calls + # something like `pending` + config.before(:context) { RSpec.current_example = nil } + + config.color_mode = :off + + ex.run + end + end +end + +RSpec.describe QA::Specs::Helpers::ContextSelector do + include Helpers::StubENV + include QA::Specs::Helpers::RSpec + + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com') + described_class.configure_rspec + end + + describe '.context_matches?' do + it 'returns true when url has .com' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.dot_com?).to be_truthy + end + + it 'returns false when url does not have .com' do + QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test") + + expect(described_class.dot_com?).to be_falsey + end + + context 'with arguments' do + it 'returns true when :subdomain is set' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.dot_com?(subdomain: :staging)).to be_truthy + end + + it 'matches multiple subdomains' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy + expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy + end + + it 'matches :production' do + QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/") + + expect(described_class.context_matches?(:production)).to be_truthy + end + + it 'doesnt match with mismatching switches' do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test') + + aggregate_failures do + expect(described_class.context_matches?(tld: '.net')).to be_falsey + expect(described_class.context_matches?(:production)).to be_falsey + expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey + expect(described_class.context_matches?(domain: 'example')).to be_falsey + end + end + end + + it 'returns false for mismatching' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.context_matches?(:production)).to be_falsey + end + end + + describe 'description and context blocks' do + context 'with environment set' do + it 'can apply to contexts or descriptions' do + group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do + it('runs in staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + end + end + + context 'with different environment set' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com') + described_class.configure_rspec + end + + it 'does not run against production' do + group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do + it('runs in staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:pending) + end + end + end + + it 'runs only in staging' do + group = describe_successfully do + it('runs in staging', only: { subdomain: :staging }) {} + it('doesnt run in staging', only: :production) {} + it('runs in staging also', only: { subdomain: %i[release staging] }) {} + it('runs in any env') {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + + context 'custom env' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net') + end + + it 'runs on a custom environment' do + group = describe_successfully do + it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {} + it('does not run on release', only: :production) {} + end + + expect(group.examples.first.execution_result.status).to eq(:passed) + expect(group.examples.last.execution_result.status).to eq(:pending) + end + end + + context 'production' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/') + end + + it 'runs on production' do + group = describe_successfully do + it('runs on prod', only: :production) {} + it('does not run in prod', only: { subdomain: :staging }) {} + it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + end + end + + it 'outputs a message for invalid environments' do + group = describe_successfully do + it('will skip', only: :production) {} + end + + expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/) + end + + context 'with pipeline constraints' do + context 'without CI_PROJECT_NAME set' do + before do + stub_env('CI_PROJECT_NAME', nil) + described_class.configure_rspec + end + + it 'runs on any pipeline' do + group = describe_successfully do + it('runs given a single named pipeline', only: { pipeline: :nightly }) {} + it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:passed) + end + end + end + + context 'when a pipeline triggered from the default branch runs in gitlab-qa' do + before do + stub_env('CI_PROJECT_NAME', 'gitlab-qa') + described_class.configure_rspec + end + + it 'runs on default branch pipelines' do + group = describe_successfully do + it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {} + it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {} + it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + end + end + end + + context 'with CI_PROJECT_NAME set' do + before do + stub_env('CI_PROJECT_NAME', 'NIGHTLY') + described_class.configure_rspec + end + + it 'runs on designated pipeline' do + group = describe_successfully do + it('runs on nightly', only: { pipeline: :nightly }) {} + it('does not run in not_nightly', only: { pipeline: :not_nightly }) {} + it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {} + it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + expect(group.examples[3].execution_result.status).to eq(:pending) + end + end + end + end + + context 'when excluding contexts' do + context 'with job constraints' do + context 'without CI_JOB_NAME set' do + before do + stub_env('CI_JOB_NAME', nil) + described_class.configure_rspec + end + + it 'runs in any job' do + group = describe_successfully do + it('runs given a single named job', exclude: { job: 'ee:instance-image' }) {} + it('runs given a single regex pattern', exclude: { job: '.*:instance-image' }) {} + it('runs given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:passed) + end + end + end + end + + context 'with CI_JOB_NAME set' do + before do + stub_env('CI_JOB_NAME', 'ee:instance-image') + described_class.configure_rspec + end + + it 'does not run in the specified job' do + group = describe_successfully do + it('skips given a single named job', exclude: { job: 'ee:instance-image' }) {} + it('skips given a single regex pattern', exclude: { job: '.*:instance-image' }) {} + it('skips given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('skips given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('skips given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:pending) + end + end + end + + it 'runs in jobs that do not match' do + group = describe_successfully do + it('runs given a single named job', exclude: { job: 'ce:instance-image' }) {} + it('runs given a single regex pattern', exclude: { job: '.*:instance-image-quarantine' }) {} + it('runs given an array of jobs', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', exclude: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:passed) + end + end + end + end + end + end +end diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb index 694c320ce3d..45754a09b17 100644 --- a/qa/spec/specs/helpers/quarantine_spec.rb +++ b/qa/spec/specs/helpers/quarantine_spec.rb @@ -2,25 +2,6 @@ require 'rspec/core/sandbox' -# We need a reporter for internal tests that's different from the reporter for -# external tests otherwise the results will be mixed up. We don't care about -# most reporting, but we do want to know if a test fails -class RaiseOnFailuresReporter < RSpec::Core::NullReporter - def self.example_failed(example) - raise example.exception - end -end - -# We use an example group wrapper to prevent the state of internal tests -# expanding into the global state -# See: https://github.com/rspec/rspec-core/issues/2603 -def describe_successfully(*args, &describe_body) - example_group = RSpec.describe(*args, &describe_body) - ran_successfully = example_group.run RaiseOnFailuresReporter - expect(ran_successfully).to eq true - example_group -end - RSpec.configure do |c| c.around do |ex| RSpec::Core::Sandbox.sandboxed do |config| @@ -38,6 +19,7 @@ end RSpec.describe QA::Specs::Helpers::Quarantine do include Helpers::StubENV + include QA::Specs::Helpers::RSpec describe '.skip_or_run_quarantined_contexts' do context 'with no tag focused' do @@ -336,159 +318,4 @@ RSpec.describe QA::Specs::Helpers::Quarantine do end end end - - describe 'running against specific environments or pipelines' do - before do - QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com') - described_class.configure_rspec - end - - describe 'description and context blocks' do - context 'with environment set' do - it 'can apply to contexts or descriptions' do - group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do - it('runs in staging') {} - end - - expect(group.examples[0].execution_result.status).to eq(:passed) - end - end - - context 'with different environment set' do - before do - QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com') - described_class.configure_rspec - end - - it 'does not run against production' do - group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do - it('runs in staging') {} - end - - expect(group.examples[0].execution_result.status).to eq(:pending) - end - end - end - - it 'runs only in staging' do - group = describe_successfully do - it('runs in staging', only: { subdomain: :staging }) {} - it('doesnt run in staging', only: :production) {} - it('runs in staging also', only: { subdomain: %i[release staging] }) {} - it('runs in any env') {} - end - - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:pending) - expect(group.examples[2].execution_result.status).to eq(:passed) - expect(group.examples[3].execution_result.status).to eq(:passed) - end - - context 'custom env' do - before do - QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net') - end - - it 'runs on a custom environment' do - group = describe_successfully do - it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {} - it('does not run on release', only: :production) {} - end - - expect(group.examples.first.execution_result.status).to eq(:passed) - expect(group.examples.last.execution_result.status).to eq(:pending) - end - end - - context 'production' do - before do - QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/') - end - - it 'runs on production' do - group = describe_successfully do - it('runs on prod', only: :production) {} - it('does not run in prod', only: { subdomain: :staging }) {} - it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {} - end - - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:pending) - expect(group.examples[2].execution_result.status).to eq(:passed) - end - end - - it 'outputs a message for invalid environments' do - group = describe_successfully do - it('will skip', only: :production) {} - end - - expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/) - end - - context 'with pipeline constraints' do - context 'without CI_PROJECT_NAME set' do - before do - stub_env('CI_PROJECT_NAME', nil) - described_class.configure_rspec - end - - it 'runs on any pipeline' do - group = describe_successfully do - it('runs given a single named pipeline', only: { pipeline: :nightly }) {} - it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {} - end - - aggregate_failures do - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:passed) - end - end - end - - context 'when a pipeline triggered from the default branch runs in gitlab-qa' do - before do - stub_env('CI_PROJECT_NAME', 'gitlab-qa') - described_class.configure_rspec - end - - it 'runs on default branch pipelines' do - group = describe_successfully do - it('runs on master pipeline given a single pipeline', only: { pipeline: :master }) {} - it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {} - it('does not run in non-default pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] }) {} - end - - aggregate_failures do - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:passed) - expect(group.examples[2].execution_result.status).to eq(:pending) - end - end - end - - context 'with CI_PROJECT_NAME set' do - before do - stub_env('CI_PROJECT_NAME', 'NIGHTLY') - described_class.configure_rspec - end - - it 'runs on designated pipeline' do - group = describe_successfully do - it('runs on nightly', only: { pipeline: :nightly }) {} - it('does not run in not_nightly', only: { pipeline: :not_nightly }) {} - it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {} - it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {} - end - - aggregate_failures do - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:pending) - expect(group.examples[2].execution_result.status).to eq(:passed) - expect(group.examples[3].execution_result.status).to eq(:pending) - end - end - end - end - end end diff --git a/qa/spec/support/helpers/stub_env.rb b/qa/spec/support/helpers/stub_env.rb index 8ad864dbec8..de8d2f47adf 100644 --- a/qa/spec/support/helpers/stub_env.rb +++ b/qa/spec/support/helpers/stub_env.rb @@ -15,7 +15,7 @@ module Helpers private - STUBBED_KEY = '__STUBBED__'.freeze + STUBBED_KEY = '__STUBBED__' def add_stubbed_value(key, value) allow(ENV).to receive(:[]).with(key).and_return(value) |