summaryrefslogtreecommitdiff
path: root/qa/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa')
-rw-r--r--qa/qa/fixtures/export.tar.gzbin306887 -> 308811 bytes
-rw-r--r--qa/qa/page/base.rb16
-rw-r--r--qa/qa/page/component/content_editor.rb54
-rw-r--r--qa/qa/page/component/import/gitlab.rb4
-rw-r--r--qa/qa/page/component/invite_members_modal.rb12
-rw-r--r--qa/qa/page/component/wiki.rb12
-rw-r--r--qa/qa/page/component/wiki_page_form.rb7
-rw-r--r--qa/qa/page/dashboard/projects.rb8
-rw-r--r--qa/qa/page/group/bulk_import.rb8
-rw-r--r--qa/qa/page/group/dependency_proxy.rb26
-rw-r--r--qa/qa/page/group/members.rb2
-rw-r--r--qa/qa/page/group/menu.rb110
-rw-r--r--qa/qa/page/group/settings/billing.rb13
-rw-r--r--qa/qa/page/merge_request/show.rb9
-rw-r--r--qa/qa/page/project/deployments/environments/show.rb23
-rw-r--r--qa/qa/page/project/import/github.rb96
-rw-r--r--qa/qa/page/project/new.rb4
-rw-r--r--qa/qa/page/project/registry/show.rb2
-rw-r--r--qa/qa/page/project/secure/configuration_form.rb31
-rw-r--r--qa/qa/page/project/wiki/edit.rb1
-rw-r--r--qa/qa/resource/base.rb21
-rw-r--r--qa/qa/resource/bulk_import_group.rb85
-rw-r--r--qa/qa/resource/group_base.rb2
-rw-r--r--qa/qa/resource/issue.rb41
-rw-r--r--qa/qa/resource/merge_request.rb13
-rw-r--r--qa/qa/resource/project.rb68
-rw-r--r--qa/qa/resource/project_imported_from_github.rb7
-rw-r--r--qa/qa/runtime/allure_report.rb21
-rw-r--r--qa/qa/runtime/api/request.rb6
-rw-r--r--qa/qa/runtime/application_settings.rb2
-rw-r--r--qa/qa/runtime/browser.rb3
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/runtime/feature.rb10
-rw-r--r--qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb102
-rw-r--r--qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb362
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb12
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb2
-rw-r--r--qa/qa/specs/features/api/5_package/container_registry_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb54
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb42
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb43
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb81
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb104
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb53
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb68
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb6
-rw-r--r--qa/qa/specs/helpers/context_formatter.rb68
-rw-r--r--qa/qa/specs/helpers/context_selector.rb12
-rw-r--r--qa/qa/specs/helpers/quarantine.rb44
-rw-r--r--qa/qa/specs/helpers/quarantine_formatter.rb45
-rw-r--r--qa/qa/specs/helpers/rspec.rb4
-rw-r--r--qa/qa/support/allure_metadata_formatter.rb37
-rw-r--r--qa/qa/support/api.rb33
-rw-r--r--qa/qa/support/repeater.rb26
-rw-r--r--qa/qa/support/retrier.rb20
65 files changed, 1578 insertions, 457 deletions
diff --git a/qa/qa/fixtures/export.tar.gz b/qa/qa/fixtures/export.tar.gz
index 08e4f0c9c43..8d27b6816ea 100644
--- a/qa/qa/fixtures/export.tar.gz
+++ b/qa/qa/fixtures/export.tar.gz
Binary files differ
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 6b54d8ab1ac..9debdc1d4dd 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -21,7 +21,7 @@ module QA
end
def to_s
- <<~MSG.strip % { page: @page_class }
+ format(<<~MSG.strip, page: @page_class)
%{page} has no required elements.
See https://docs.gitlab.com/ee/development/testing_guide/end_to_end/dynamic_element_validation.html#required-elements
MSG
@@ -108,7 +108,7 @@ module QA
wait_for_requests(skip_finished_loading_check: skip_finished_loading_check)
element_selector = element_selector_css(name, reject_capybara_query_keywords(kwargs))
- find(element_selector, only_capybara_query_keywords(kwargs))
+ find(element_selector, **only_capybara_query_keywords(kwargs))
end
def only_capybara_query_keywords(kwargs)
@@ -232,11 +232,11 @@ module QA
visible = kwargs.delete(:visible)
visible = visible.nil? && true
- try_find_element = ->(wait) do
+ try_find_element = lambda do |wait|
if disabled.nil?
has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass, visible: visible)
else
- find_element(name, original_kwargs).disabled? == disabled
+ find_element(name, **original_kwargs).disabled? == disabled
end
end
@@ -322,13 +322,13 @@ module QA
# It would be ideal if we could detect when the animation is complete
# but in some cases there's nothing we can easily access via capybara
# so instead we wait for the element, and then we wait a little longer
- raise ElementNotFound, %Q(Couldn't find element named "#{name}") unless has_element?(name)
+ raise ElementNotFound, %(Couldn't find element named "#{name}") unless has_element?(name)
sleep 1
end
def within_element(name, **kwargs)
- wait_for_requests
+ wait_for_requests(skip_finished_loading_check: kwargs.delete(:skip_finished_loading_check))
text = kwargs.delete(:text)
page.within(element_selector_css(name, kwargs), text: text) do
@@ -386,9 +386,7 @@ module QA
end
def self.errors
- if views.empty?
- return ["Page class does not have views / elements defined!"]
- end
+ return ["Page class does not have views / elements defined!"] if views.empty?
views.flat_map(&:errors)
end
diff --git a/qa/qa/page/component/content_editor.rb b/qa/qa/page/component/content_editor.rb
new file mode 100644
index 00000000000..b3a42634fe7
--- /dev/null
+++ b/qa/qa/page/component/content_editor.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module ContentEditor
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/content_editor/components/content_editor.vue' do
+ element :content_editor_container
+ end
+
+ base.view 'app/assets/javascripts/content_editor/components/toolbar_text_style_dropdown.vue' do
+ element :text_style_dropdown
+ element :text_style_menu_item
+ end
+
+ base.view 'app/assets/javascripts/content_editor/components/toolbar_image_button.vue' do
+ element :file_upload_field
+ end
+ end
+
+ def add_heading(heading, text)
+ within_element(:content_editor_container) do
+ text_area.set(text)
+ # wait for text style option to become active after typing
+ has_active_element?(:text_style_dropdown, wait: 1)
+ click_element(:text_style_dropdown)
+ within_element(:text_style_dropdown) do
+ click_element(:text_style_menu_item, text_style: heading)
+ end
+ end
+ end
+
+ def upload_image(image_path)
+ within_element(:content_editor_container) do
+ # add image on a new line
+ text_area.send_keys(:return)
+ find_element(:file_upload_field, visible: false).send_keys(image_path)
+ end
+ end
+
+ private
+
+ def text_area
+ find('[contenteditable="true"]', visible: false)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/import/gitlab.rb b/qa/qa/page/component/import/gitlab.rb
index 2fd2a45b399..5831a713ae5 100644
--- a/qa/qa/page/component/import/gitlab.rb
+++ b/qa/qa/page/component/import/gitlab.rb
@@ -5,6 +5,8 @@ module QA
module Component
module Import
module Gitlab
+ extend QA::Page::PageConcern
+
def self.included(base)
super
@@ -30,7 +32,7 @@ module QA
click_element(:import_project_button)
wait_until(reload: false) do
- has_notice?("The project was successfully imported.")
+ has_notice?("The project was successfully imported.") || has_element?(:project_name_content)
end
end
end
diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb
index 7cec4588af5..fecd61fb410 100644
--- a/qa/qa/page/component/invite_members_modal.rb
+++ b/qa/qa/page/component/invite_members_modal.rb
@@ -40,18 +40,20 @@ module QA
click_element :invite_a_group_button
end
- def add_member(username, access_level = Resource::Members::AccessLevel::DEVELOPER)
+ def add_member(username, access_level = 'Developer')
open_invite_members_modal
within_element(:invite_members_modal_content) do
- fill_element :access_level_dropdown, with: access_level
-
fill_element :members_token_select_input, username
-
Support::WaitForRequests.wait_for_requests
-
click_button username
+ # Guest option is selected by default, skipping these steps if desired option is 'Guest'
+ unless access_level == 'Guest'
+ click_element :access_level_dropdown
+ click_button access_level
+ end
+
click_element :invite_button
end
diff --git a/qa/qa/page/component/wiki.rb b/qa/qa/page/component/wiki.rb
index 92eb25af247..c3db1d6c885 100644
--- a/qa/qa/page/component/wiki.rb
+++ b/qa/qa/page/component/wiki.rb
@@ -68,6 +68,18 @@ module QA
def has_no_page?
has_element?(:create_first_page_link)
end
+
+ def has_heading?(heading_type, text)
+ within_element(:wiki_page_content) do
+ has_css?(heading_type, text: text)
+ end
+ end
+
+ def has_image?(image_file_name)
+ within_element(:wiki_page_content) do
+ has_css?("img[src$='#{image_file_name}']")
+ end
+ end
end
end
end
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index bb22b7da003..6b7452b0e0f 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -14,6 +14,7 @@ module QA
element :wiki_content_textarea
element :wiki_message_textbox
element :wiki_submit_button
+ element :try_new_editor_container
end
base.view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
@@ -41,6 +42,12 @@ module QA
click_element(:delete_button, Page::Modal::DeleteWiki)
Page::Modal::DeleteWiki.perform(&:confirm_deletion)
end
+
+ def use_new_editor
+ within_element(:try_new_editor_container) do
+ click_button('Use the new editor')
+ end
+ end
end
end
end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 8be11550233..c0108d85365 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -13,6 +13,10 @@ module QA
element :user_role_content
end
+ view 'app/views/dashboard/_projects_head.html.haml' do
+ element :new_project_button
+ end
+
def has_project_with_access_role?(project_name, access_role)
within_element(:project_content, text: project_name) do
has_element?(:user_role_content, text: access_role)
@@ -25,6 +29,10 @@ module QA
find_link(text: name).click
end
+ def click_new_project_button
+ click_element(:new_project_button, Page::Project::New)
+ end
+
def self.path
'/'
end
diff --git a/qa/qa/page/group/bulk_import.rb b/qa/qa/page/group/bulk_import.rb
index a0511c9a16c..9ba80abf21c 100644
--- a/qa/qa/page/group/bulk_import.rb
+++ b/qa/qa/page/group/bulk_import.rb
@@ -6,13 +6,13 @@ module QA
class BulkImport < Page::Base
view "app/assets/javascripts/import_entities/import_groups/components/import_table.vue" do
element :import_table
+ element :import_item
+ element :import_group_button
+ element :import_status_indicator
end
- view "app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue" do
- element :import_item
+ view "app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue" do
element :target_group_dropdown_item
- element :import_status_indicator
- element :import_group_button
end
view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do
diff --git a/qa/qa/page/group/dependency_proxy.rb b/qa/qa/page/group/dependency_proxy.rb
new file mode 100644
index 00000000000..f637c79cffc
--- /dev/null
+++ b/qa/qa/page/group/dependency_proxy.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Group
+ class DependencyProxy < QA::Page::Base
+ view 'app/views/groups/dependency_proxies/show.html.haml' do
+ element :dependency_proxy_setting_toggle
+ end
+
+ view 'app/views/groups/dependency_proxies/_url.html.haml' do
+ element :dependency_proxy_count
+ end
+
+ def has_dependency_proxy_enabled?
+ toggle = find_element(:dependency_proxy_setting_toggle)
+ toggle[:class].include?('is-checked')
+ end
+
+ def has_blob_count?(blob_text)
+ has_element?(:dependency_proxy_count, text: blob_text)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/members.rb b/qa/qa/page/group/members.rb
index b526a4488b2..ccc901932f4 100644
--- a/qa/qa/page/group/members.rb
+++ b/qa/qa/page/group/members.rb
@@ -7,7 +7,7 @@ module QA
include Page::Component::InviteMembersModal
include Page::Component::UsersSelect
- view 'app/assets/javascripts/vue_shared/components/remove_member_modal.vue' do
+ view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do
element :remove_member_modal_content
end
diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb
index 338a135614d..c997598e25a 100644
--- a/qa/qa/page/group/menu.rb
+++ b/qa/qa/page/group/menu.rb
@@ -6,51 +6,32 @@ module QA
class Menu < Page::Base
include SubMenus::Common
- view 'app/views/layouts/nav/sidebar/_group_menus.html.haml' do
- element :general_settings_link
- element :group_issues_item
- element :group_members_item
- element :group_milestones_link
- element :group_settings
- element :group_information_link
- element :group_information_submenu
- end
-
- view 'app/views/groups/sidebar/_packages_settings.html.haml' do
- element :group_package_settings_link
- end
-
- view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do
- element :analytics_link
- element :analytics_sidebar_submenu
- end
-
def click_group_members_item
- hover_element(:group_information_link) do
- within_submenu(:group_information_submenu) do
- click_element(:group_members_item)
+ hover_group_information do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Members')
end
end
end
- def click_settings
- within_sidebar do
- click_element(:group_settings)
+ def click_subgroup_members_item
+ hover_subgroup_information do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Members')
+ end
end
end
- def click_contribution_analytics_item
- hover_element(:analytics_link) do
- within_submenu(:analytics_sidebar_submenu) do
- click_element(:contribution_analytics_link)
- end
+ def click_settings
+ within_sidebar do
+ click_element(:sidebar_menu_link, menu_item: 'Settings')
end
end
def click_group_general_settings_item
- hover_element(:group_settings) do
- within_submenu(:group_sidebar_submenu) do
- click_element(:general_settings_link)
+ hover_group_settings do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'General')
end
end
end
@@ -58,16 +39,31 @@ module QA
def go_to_milestones
hover_issues do
within_submenu do
- click_element(:group_milestones_link)
+ click_element(:sidebar_menu_item_link, menu_item: 'Milestones')
end
end
end
def go_to_package_settings
- scroll_to_element(:group_settings)
- hover_element(:group_settings) do
- within_submenu(:group_sidebar_submenu) do
- click_element(:group_package_settings_link)
+ hover_group_settings do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Packages & Registries')
+ end
+ end
+ end
+
+ def go_to_group_packages
+ hover_group_packages do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Package Registry')
+ end
+ end
+ end
+
+ def go_to_dependency_proxy
+ hover_group_packages do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Dependency Proxy')
end
end
end
@@ -76,8 +72,42 @@ module QA
def hover_issues
within_sidebar do
- scroll_to_element(:group_issues_item)
- find_element(:group_issues_item).hover
+ scroll_to_element(:sidebar_menu_link, menu_item: 'Issues')
+ find_element(:sidebar_menu_link, menu_item: 'Issues').hover
+
+ yield
+ end
+ end
+
+ def hover_group_information
+ within_sidebar do
+ find_element(:sidebar_menu_link, menu_item: 'Group information').hover
+
+ yield
+ end
+ end
+
+ def hover_subgroup_information
+ within_sidebar do
+ find_element(:sidebar_menu_link, menu_item: 'Subgroup information').hover
+
+ yield
+ end
+ end
+
+ def hover_group_packages
+ within_sidebar do
+ scroll_to_element(:sidebar_menu_link, menu_item: 'Packages & Registries')
+ find_element(:sidebar_menu_link, menu_item: 'Packages & Registries').hover
+
+ yield
+ end
+ end
+
+ def hover_group_settings
+ within_sidebar do
+ scroll_to_element(:sidebar_menu_link, menu_item: 'Settings')
+ find_element(:sidebar_menu_link, menu_item: 'Settings').hover
yield
end
diff --git a/qa/qa/page/group/settings/billing.rb b/qa/qa/page/group/settings/billing.rb
deleted file mode 100644
index a83af47fc35..00000000000
--- a/qa/qa/page/group/settings/billing.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Group
- module Settings
- class Billing < Chemlab::Page
- link :start_your_free_trial
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 5f52d48e9f6..afe88fc0cdc 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -202,7 +202,9 @@ module QA
def has_pipeline_status?(text)
# Pipelines can be slow, so we wait a bit longer than the usual 10 seconds
- has_element?(:merge_request_pipeline_info_content, text: text, wait: 60)
+ wait_until(sleep_interval: 5, reload: false) do
+ has_element?(:merge_request_pipeline_info_content, text: text, wait: 15 )
+ end
end
def has_title?(title)
@@ -236,7 +238,10 @@ module QA
end
def merged?
- # Revisit after merge page re-architect is done https://gitlab.com/gitlab-org/gitlab/-/issues/300042
+ # Reloads the page at this point to avoid the problem of the merge status failing to update
+ # That's the transient UX issue this test is checking for, so if the MR is merged but the UI still shows the
+ # status as unmerged, the test will fail.
+ # Revisit after merge page re-architect is done https://gitlab.com/groups/gitlab-org/-/epics/5598
# To remove page refresh logic if possible
retry_until(max_attempts: 3, reload: true) do
has_element?(:merged_status_content, text: 'The changes were merged into', wait: 20)
diff --git a/qa/qa/page/project/deployments/environments/show.rb b/qa/qa/page/project/deployments/environments/show.rb
deleted file mode 100644
index 48e4850d3be..00000000000
--- a/qa/qa/page/project/deployments/environments/show.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Deployments
- module Environments
- class Show < Page::Base
- view 'app/views/projects/environments/_external_url.html.haml' do
- element :view_deployment
- end
-
- def view_deployment(&block)
- new_window = window_opened_by { click_element(:view_deployment) }
-
- within_window(new_window, &block) if block
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index 74bc4cec467..bb35c5eb17c 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -18,12 +18,17 @@ module QA
element :import_button
element :project_path_content
element :go_to_project_button
+ element :import_status_indicator
end
view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do
element :target_namespace_selector_dropdown
end
+ # Add personal access token
+ #
+ # @param [String] personal_access_token
+ # @return [void]
def add_personal_access_token(personal_access_token)
# If for some reasons this process is retried, user cannot re-enter github token in the same group
# In this case skip this step and proceed to import project row
@@ -34,71 +39,50 @@ module QA
finished_loading?
end
- def import!(full_path, name)
- return if already_imported(full_path)
-
- choose_test_namespace(full_path)
- set_path(full_path, name)
- import_project(full_path)
-
- wait_for_success
- end
-
- # TODO: refactor to use 'go to project' button instead of generic main menu
- def go_to_project(name)
- Page::Main::Menu.perform(&:go_to_projects)
- Page::Dashboard::Projects.perform do |dashboard|
- dashboard.go_to_project(name)
- end
- end
-
- private
-
- def within_repo_path(full_path, &block)
- project_import_row = find_element(:project_import_row, text: full_path)
-
- within(project_import_row, &block)
- end
-
- def choose_test_namespace(full_path)
- within_repo_path(full_path) do
- within_element(:target_namespace_selector_dropdown) { click_button(class: 'dropdown-toggle') }
- click_element(:target_group_dropdown_item, group_name: Runtime::Namespace.path)
- end
- end
-
- def set_path(full_path, name)
- within_repo_path(full_path) do
- fill_element(:project_path_field, name)
- end
- end
-
- def import_project(full_path)
- within_repo_path(full_path) do
+ # Import project
+ #
+ # @param [String] source_project_name
+ # @param [String] target_group_path
+ # @return [void]
+ def import!(gh_project_name, target_group_path, project_name)
+ within_element(:project_import_row, source_project: gh_project_name) do
+ click_element(:target_namespace_selector_dropdown)
+ click_element(:target_group_dropdown_item, group_name: target_group_path)
+ fill_element(:project_path_field, project_name)
click_element(:import_button)
end
end
- def wait_for_success
- # TODO: set reload:false and remove skip_finished_loading_check_on_refresh when
- # https://gitlab.com/gitlab-org/gitlab/-/issues/292861 is fixed
- wait_until(
- max_duration: 90,
- sleep_interval: 5.0,
- reload: true,
- skip_finished_loading_check_on_refresh: true
- ) do
- # TODO: Refactor to explicitly wait for specific project import successful status
- # This check can create false positive if main importing message appears with delay and check exits early
- page.has_no_content?('Importing 1 repository', wait: 3)
+ # Check Go to project button present
+ #
+ # @param [String] gh_project_name
+ # @return [Boolean]
+ def has_go_to_project_button?(gh_project_name)
+ within_element(:project_import_row, source_project: gh_project_name) do
+ has_element?(:go_to_project_button)
end
end
- def already_imported(full_path)
- within_repo_path(full_path) do
- has_element?(:project_path_content) && has_element?(:go_to_project_button)
+ # Check if import page has a successfully imported project
+ #
+ # @param [String] source_project_name
+ # @param [Integer] wait
+ # @return [Boolean]
+ def has_imported_project?(gh_project_name, wait: QA::Support::WaitForRequests::DEFAULT_MAX_WAIT_TIME)
+ within_element(:project_import_row, source_project: gh_project_name, skip_finished_loading_check: true) do
+ # TODO: remove retrier with reload:true once https://gitlab.com/gitlab-org/gitlab/-/issues/292861 is fixed
+ wait_until(
+ max_duration: wait,
+ sleep_interval: 5,
+ reload: true,
+ skip_finished_loading_check_on_refresh: true
+ ) do
+ has_element?(:import_status_indicator, text: "Complete")
+ end
end
end
+
+ alias_method :wait_for_success, :has_imported_project?
end
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 170cc14b27f..06e476f009a 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -83,8 +83,8 @@ module QA
click_button 'Repo by URL'
end
- def enable_initialize_with_readme
- check_element(:initialize_with_readme_checkbox)
+ def disable_initialize_with_readme
+ uncheck_element(:initialize_with_readme_checkbox)
end
end
end
diff --git a/qa/qa/page/project/registry/show.rb b/qa/qa/page/project/registry/show.rb
index dffdb9eebba..03c547fc8b5 100644
--- a/qa/qa/page/project/registry/show.rb
+++ b/qa/qa/page/project/registry/show.rb
@@ -31,7 +31,7 @@ module QA
def click_delete
click_element(:tag_delete_button)
- find_button('Confirm').click
+ find_button('Delete').click
end
end
end
diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb
new file mode 100644
index 00000000000..73d1601b61e
--- /dev/null
+++ b/qa/qa/page/project/secure/configuration_form.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Secure
+ class ConfigurationForm < QA::Page::Base
+ include QA::Page::Component::Select2
+ include QA::Page::Settings::Common
+
+ view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do
+ element :sast_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
+ element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern
+ end
+
+ def click_sast_enable_button
+ click_element(:sast_enable_button)
+ end
+
+ def has_sast_status?(status_text)
+ within_element(:sast_status) do
+ has_text?(status_text)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Secure::ConfigurationForm.prepend_mod_with('Page::Project::Secure::ConfigurationForm', namespace: QA)
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index 70aa10cc43e..e782bbbb432 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -7,6 +7,7 @@ module QA
class Edit < Base
include Page::Component::WikiPageForm
include Page::Component::WikiSidebar
+ include Page::Component::ContentEditor
end
end
end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index ca0087cf709..2848e3ba7d2 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -75,19 +75,18 @@ module QA
end
def log_fabrication(method, resource, parents, args)
- return yield unless Runtime::Env.debug?
-
start = Time.now
- prefix = "==#{'=' * parents.size}>"
- msg = [prefix]
- msg << "Built a #{name}"
- msg << "as a dependency of #{parents.last}" if parents.any?
- msg << "via #{method}"
yield.tap do
- msg << "in #{Time.now - start} seconds"
- puts msg.join(' ')
- puts if parents.empty?
+ Runtime::Logger.debug do
+ msg = ["==#{'=' * parents.size}>"]
+ msg << "Built a #{name}"
+ msg << "as a dependency of #{parents.last}" if parents.any?
+ msg << "via #{method}"
+ msg << "in #{Time.now - start} seconds"
+
+ msg.join(' ')
+ end
end
end
@@ -189,7 +188,7 @@ module QA
end
def log_having_both_api_result_and_block(name, api_value)
- QA::Runtime::Logger.info(<<~MSG.strip)
+ QA::Runtime::Logger.debug(<<~MSG.strip)
<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored.
MSG
end
diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb
new file mode 100644
index 00000000000..5380bb16f10
--- /dev/null
+++ b/qa/qa/resource/bulk_import_group.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class BulkImportGroup < Group
+ attributes :source_group_path,
+ :import_id
+
+ attribute :destination_group_path do
+ source_group_path
+ end
+
+ attribute :access_token do
+ api_client.personal_access_token
+ end
+
+ alias_method :path, :source_group_path
+
+ delegate :gitlab_address, to: 'QA::Runtime::Scenario'
+
+ def fabricate_via_browser_ui!
+ Page::Main::Menu.perform(&:go_to_create_group)
+
+ Page::Group::New.perform do |group|
+ group.switch_to_import_tab
+ group.connect_gitlab_instance(gitlab_address, api_client.personal_access_token)
+ end
+
+ Page::Group::BulkImport.perform do |import_page|
+ import_page.import_group(path, sandbox.path)
+ end
+
+ reload!
+ visit!
+ end
+
+ def fabricate_via_api!
+ resource_web_url(api_post)
+ end
+
+ def api_post_path
+ '/bulk_imports'
+ end
+
+ def api_post_body
+ {
+ configuration: {
+ url: gitlab_address,
+ access_token: access_token
+ },
+ entities: [
+ {
+ source_type: 'group_entity',
+ source_full_path: source_group_path,
+ destination_name: destination_group_path,
+ destination_namespace: sandbox.path
+ }
+ ]
+ }
+ end
+
+ def import_status
+ response = get(Runtime::API::Request.new(api_client, "/bulk_imports/#{import_id}").url)
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceQueryError, "Could not get import status. Request returned (#{response.code}): `#{response}`."
+ end
+
+ parse_body(response)[:status]
+ end
+
+ private
+
+ def transform_api_resource(api_resource)
+ return api_resource if api_resource[:web_url]
+
+ # override transformation only for /bulk_imports endpoint which doesn't have web_url in response and
+ # ignore others so import_id is not overwritten incorrectly
+ api_resource[:web_url] = "#{gitlab_address}/#{full_path}"
+ api_resource[:import_id] = api_resource[:id]
+ api_resource
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb
index 652c6cf7d1e..b937b704613 100644
--- a/qa/qa/resource/group_base.rb
+++ b/qa/qa/resource/group_base.rb
@@ -101,3 +101,5 @@ module QA
end
end
end
+
+QA::Resource::GroupBase.prepend_mod_with('Resource::GroupBase', namespace: QA)
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index ffffa0eecda..c45ab7593b6 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -14,11 +14,11 @@ module QA
end
end
- attribute :id
- attribute :iid
- attribute :assignee_ids
- attribute :labels
- attribute :title
+ attributes :id,
+ :iid,
+ :assignee_ids,
+ :labels,
+ :title
def initialize
@assignee_ids = []
@@ -41,13 +41,21 @@ module QA
end
def api_get_path
- "/projects/#{project.id}/issues/#{id}"
+ "/projects/#{project.id}/issues/#{iid}"
end
def api_post_path
"/projects/#{project.id}/issues"
end
+ def api_put_path
+ "/projects/#{project.id}/issues/#{iid}"
+ end
+
+ def api_comments_path
+ "#{api_get_path}/notes"
+ end
+
def api_post_body
{
assignee_ids: assignee_ids,
@@ -59,20 +67,31 @@ module QA
end
end
- def api_put_path
- "/projects/#{project.id}/issues/#{iid}"
- end
-
def set_issue_assignees(assignee_ids:)
put_body = { assignee_ids: assignee_ids }
response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
unless response.code == HTTP_STATUS_OK
- raise ResourceUpdateFailedError, "Could not update issue assignees to #{assignee_ids}. Request returned (#{response.code}): `#{response}`."
+ raise(
+ ResourceUpdateFailedError,
+ "Could not update issue assignees to #{assignee_ids}. Request returned (#{response.code}): `#{response}`."
+ )
end
QA::Runtime::Logger.debug("Successfully updated issue assignees to #{assignee_ids}")
end
+
+ # Get issue comments
+ #
+ # @return [Array]
+ def comments(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_comments_path)) unless auto_paginate
+
+ auto_paginated_response(
+ Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url,
+ attempts: attempts
+ )
+ end
end
end
end
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 8d9de0ea718..8c313f5d518 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -152,7 +152,8 @@ module QA
@project = Resource::ImportProject.fabricate_via_browser_ui!
# Setting the name here, since otherwise some tests will look for an existing file in
# the proejct without ever knowing what is in it.
- @file_name = "github_controller_spec.rb"
+ @file_name = "added_file-00000000.txt"
+ @source_branch = "large_merge_request"
visit("#{project.web_url}/-/merge_requests/1")
current_url
end
@@ -160,9 +161,13 @@ module QA
# Get MR comments
#
# @return [Array]
- def comments
- response = get(Runtime::API::Request.new(api_client, api_comments_path).url)
- parse_body(response)
+ def comments(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_comments_path)) unless auto_paginate
+
+ auto_paginated_response(
+ Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url,
+ attempts: attempts
+ )
end
private
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index d111b070863..53b8a9b0246 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -13,13 +13,14 @@ module QA
:initialize_with_readme,
:auto_devops_enabled,
:github_personal_access_token,
- :github_repository_path
+ :github_repository_path,
+ :gitlab_repository_path
attributes :id,
:name,
:add_name_uuid,
:description,
- :standalone,
+ :personal_namespace,
:runners_token,
:visibility,
:template_name,
@@ -32,7 +33,7 @@ module QA
end
attribute :path_with_namespace do
- "#{sandbox_path}#{group.path}/#{name}" if group
+ "#{group.full_path}/#{name}"
end
alias_method :full_path, :path_with_namespace
@@ -51,7 +52,7 @@ module QA
def initialize
@add_name_uuid = true
- @standalone = false
+ @personal_namespace = false
@description = 'My awesome project'
@initialize_with_readme = false
@auto_devops_enabled = false
@@ -69,7 +70,9 @@ module QA
def fabricate!
return if @import
- unless @standalone
+ if @personal_namespace
+ Page::Dashboard::Projects.perform(&:click_new_project_button)
+ else
group.visit!
Page::Group::Show.perform(&:go_to_new_project)
end
@@ -84,13 +87,15 @@ module QA
Page::Project::New.perform(&:click_blank_project_link)
Page::Project::New.perform do |new_page|
- new_page.choose_test_namespace
+ new_page.choose_test_namespace unless @personal_namespace
new_page.choose_name(@name)
new_page.add_description(@description)
new_page.set_visibility(@visibility)
- new_page.enable_initialize_with_readme if @initialize_with_readme
+ new_page.disable_initialize_with_readme unless @initialize_with_readme
new_page.create_new_project
end
+
+ @id = Page::Project::Show.perform(&:project_id)
end
def fabricate_via_api!
@@ -218,7 +223,7 @@ module QA
auto_devops_enabled: @auto_devops_enabled
}
- unless @standalone
+ unless @personal_namespace
post_body[:namespace_id] = group.id
post_body[:path] = name
end
@@ -263,19 +268,24 @@ module QA
result = parse_body(response)
- Runtime::Logger.error("Import failed: #{result[:import_error]}") if result[:import_status] == "failed"
+ if result[:import_status] == "failed"
+ Runtime::Logger.error("Import failed: #{result[:import_error]}")
+ Runtime::Logger.error("Failed relations: #{result[:failed_relations]}")
+ end
result[:import_status]
end
- def commits
- response = get(request_url(api_commits_path))
- parse_body(response)
+ def commits(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_commits_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_commits_path, per_page: '100'), attempts: attempts)
end
- def merge_requests
- response = get(request_url(api_merge_requests_path))
- parse_body(response)
+ def merge_requests(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_merge_requests_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_merge_requests_path, per_page: '100'), attempts: attempts)
end
def merge_request_with_title(title)
@@ -299,9 +309,10 @@ module QA
parse_body(response)
end
- def repository_branches
- response = get(request_url(api_repository_branches_path))
- parse_body(response)
+ def repository_branches(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_repository_branches_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_repository_branches_path, per_page: '100'), attempts: attempts)
end
def repository_tags
@@ -324,19 +335,22 @@ module QA
parse_body(response)
end
- def issues
- response = get(request_url(api_issues_path))
- parse_body(response)
+ def issues(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_issues_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_issues_path, per_page: '100'), attempts: attempts)
end
- def labels
- response = get(request_url(api_labels_path))
- parse_body(response)
+ def labels(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_labels_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_labels_path, per_page: '100'), attempts: attempts)
end
- def milestones
- response = get(request_url(api_milestones_path))
- parse_body(response)
+ def milestones(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_milestones_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_milestones_path, per_page: '100'), attempts: attempts)
end
def wikis
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index 214e8f517bb..8aa19555d50 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -18,9 +18,12 @@ module QA
Page::Project::Import::Github.perform do |import_page|
import_page.add_personal_access_token(github_personal_access_token)
- import_page.import!(github_repository_path, name)
- import_page.go_to_project(name)
+ import_page.import!(github_repository_path, group.full_path, name)
+ import_page.wait_for_success(github_repository_path)
end
+
+ reload!
+ visit!
end
def go_to_import_page
diff --git a/qa/qa/runtime/allure_report.rb b/qa/qa/runtime/allure_report.rb
index bcfdb09e09f..bf49141566a 100644
--- a/qa/qa/runtime/allure_report.rb
+++ b/qa/qa/runtime/allure_report.rb
@@ -67,25 +67,8 @@ module QA
# @return [void]
def configure_rspec
RSpec.configure do |config|
- config.formatter = AllureRspecFormatter
-
- config.after do |example|
- next if example.attempts && example.attempts > 0
-
- testcase = example.metadata[:testcase]
- example.tms('Testcase', testcase) if testcase
-
- quarantine_issue = example.metadata.dig(:quarantine, :issue)
- example.issue('Quarantine issue', quarantine_issue) if quarantine_issue
-
- spec_file = example.file_path.split('/').last
- example.issue(
- 'Failure issues',
- "https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}"
- )
-
- example.add_link(name: "Job(#{Env.ci_job_name})", url: Env.ci_job_url) if Env.running_in_ci?
- end
+ config.add_formatter(AllureRspecFormatter)
+ config.add_formatter(QA::Support::AllureMetadataFormatter)
end
end
diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb
index 28bae541cb8..c1df5e84f6c 100644
--- a/qa/qa/runtime/api/request.rb
+++ b/qa/qa/runtime/api/request.rb
@@ -6,6 +6,10 @@ module QA
class Request
API_VERSION = 'v4'
+ def self.masked_url(url)
+ url.sub(/private_token=.*/, "private_token=[****]")
+ end
+
def initialize(api_client, path, **query_string)
query_string[:private_token] ||= api_client.personal_access_token unless query_string[:oauth_access_token]
request_path = request_path(path, **query_string)
@@ -13,7 +17,7 @@ module QA
end
def mask_url
- @session_address.address.sub(/private_token=.*/, "private_token=[****]")
+ QA::Runtime::API::Request.masked_url(url)
end
def url
diff --git a/qa/qa/runtime/application_settings.rb b/qa/qa/runtime/application_settings.rb
index 428ed20c83f..0b2aef47576 100644
--- a/qa/qa/runtime/application_settings.rb
+++ b/qa/qa/runtime/application_settings.rb
@@ -26,7 +26,7 @@ module QA
end
def restore_application_settings(*application_settings_keys)
- set_application_settings(@original_application_settings.slice(*application_settings_keys))
+ set_application_settings(**@original_application_settings.slice(*application_settings_keys))
end
private
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index e13061e2648..9097690de57 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -143,11 +143,12 @@ module QA
if QA::Runtime::Env.remote_grid
selenium_options[:url] = QA::Runtime::Env.remote_grid
capabilities[:browserVersion] = 'latest'
+ capabilities['sauce:options'] = { tunnelIdentifier: QA::Runtime::Env.remote_tunnel_id }
end
Capybara::Selenium::Driver.new(
app,
- selenium_options
+ **selenium_options
)
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 5cac811d95b..a076d8db9e0 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -142,6 +142,10 @@ module QA
ENV['QA_REMOTE_GRID_PROTOCOL'] || 'http'
end
+ def remote_tunnel_id
+ ENV['QA_REMOTE_TUNNEL_ID'] || 'gitlab-sl_tunnel_id'
+ end
+
def browser
ENV['QA_BROWSER'].nil? ? :chrome : ENV['QA_BROWSER'].to_sym
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index dd7f9cf898c..7011f46542b 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -32,7 +32,7 @@ module QA
def enabled?(key, **scopes)
feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
- feature && (feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], scopes))
+ feature && (feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes))
end
private
@@ -43,7 +43,7 @@ module QA
raise AuthorizationError, "Administrator access is required to enable/disable feature flags. #{e.message}"
end
- def enabled_scope?(gates, scopes)
+ def enabled_scope?(gates, **scopes)
scopes.each do |key, value|
case key
when :project, :group, :user
@@ -71,16 +71,16 @@ module QA
# scopes: Any scope (user, project, group) to restrict the change to
def set_and_verify(key, enable:, **scopes)
msg = "#{enable ? 'En' : 'Dis'}abling feature: #{key}"
- msg += " for scope \"#{scopes_to_s(scopes)}\"" if scopes.present?
+ msg += " for scope \"#{scopes_to_s(**scopes)}\"" if scopes.present?
QA::Runtime::Logger.info(msg)
Support::Retrier.retry_on_exception(sleep_interval: 2) do
- set_feature(key, enable, scopes)
+ set_feature(key, enable, **scopes)
is_enabled = nil
QA::Support::Waiter.wait_until(sleep_interval: 1) do
- is_enabled = enabled?(key, scopes)
+ is_enabled = enabled?(key, **scopes)
is_enabled == enable || !enable && scopes.present?
end
diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb
new file mode 100644
index 00000000000..6bbb859b3ee
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage', :requires_admin do
+ describe 'Bulk group import' do
+ let!(:staging?) { Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') }
+
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+ let(:user) do
+ Resource::User.fabricate_via_api! do |usr|
+ usr.api_client = admin_api_client
+ usr.hard_delete_on_api_removal = true
+ end
+ end
+
+ let(:api_client) { Runtime::API::Client.new(user: user) }
+ let(:personal_access_token) { api_client.personal_access_token }
+
+ let(:sandbox) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = admin_api_client
+ end
+ end
+
+ let(:source_group) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
+ end
+ end
+
+ let(:subgroup) do
+ Resource::Group.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.sandbox = source_group
+ group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
+ end
+ end
+
+ let(:imported_subgroup) do
+ Resource::Group.init do |group|
+ group.api_client = api_client
+ group.sandbox = imported_group
+ group.path = subgroup.path
+ end
+ end
+
+ let(:imported_group) do
+ Resource::BulkImportGroup.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.sandbox = sandbox
+ group.source_group_path = source_group.path
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(:bulk_import) unless staging?
+ Runtime::Feature.enable(:top_level_group_creation_enabled) if staging?
+
+ sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+
+ Resource::GroupLabel.fabricate_via_api! do |label|
+ label.api_client = api_client
+ label.group = source_group
+ label.title = "source-group-#{SecureRandom.hex(4)}"
+ end
+ Resource::GroupLabel.fabricate_via_api! do |label|
+ label.api_client = api_client
+ label.group = subgroup
+ label.title = "subgroup-#{SecureRandom.hex(4)}"
+ end
+ end
+
+ # Non blocking issues:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/331252
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back
+ it(
+ 'imports group with subgroups and labels',
+ testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1871'
+ ) do
+ expect { imported_group.import_status }.to(
+ eventually_eq('finished').within(max_duration: 300, sleep_interval: 2)
+ )
+
+ aggregate_failures do
+ expect(imported_group.reload!).to eq(source_group)
+ expect(imported_group.labels).to include(*source_group.labels)
+
+ expect(imported_subgroup.reload!).to eq(subgroup)
+ expect(imported_subgroup.labels).to include(*subgroup.labels)
+ end
+ end
+
+ after do
+ user.remove_via_api!
+ ensure
+ Runtime::Feature.disable(:bulk_import) unless staging?
+ Runtime::Feature.disable(:top_level_group_creation_enabled) if staging?
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
index 1b873d35d75..72a0a761294 100644
--- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
@@ -33,7 +33,7 @@ module QA
it 'imports Github repo via api', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1858' do
imported_project # import the project
- expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(duration: 90)
+ expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(max_duration: 90)
aggregate_failures do
verify_repository_import
diff --git a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
new file mode 100644
index 00000000000..385908f2176
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
@@ -0,0 +1,362 @@
+# frozen_string_literal: true
+
+require 'octokit'
+
+# rubocop:disable Rails/Pluck
+module QA
+ # Only executes in custom job/pipeline
+ RSpec.describe 'Manage', :github, :requires_admin, only: { job: 'large-github-import' } do
+ describe 'Project import' do
+ let(:logger) { Runtime::Logger.logger }
+ let(:differ) { RSpec::Support::Differ.new(color: true) }
+
+ let(:api_client) { Runtime::API::Client.as_admin }
+ let(:group) do
+ Resource::Group.fabricate_via_api! do |resource|
+ resource.api_client = api_client
+ end
+ end
+
+ let(:user) do
+ Resource::User.fabricate_via_api! do |resource|
+ resource.api_client = api_client
+ end
+ end
+
+ let(:github_repo) { ENV['QA_LARGE_GH_IMPORT_REPO'] || 'rspec/rspec-core' }
+ let(:import_max_duration) { ENV['QA_LARGE_GH_IMPORT_DURATION'] ? ENV['QA_LARGE_GH_IMPORT_DURATION'].to_i : 7200 }
+ let(:github_client) do
+ Octokit.middleware = Faraday::RackBuilder.new do |builder|
+ builder.response(:logger, logger, headers: false, bodies: false)
+ end
+
+ Octokit::Client.new(
+ access_token: ENV['QA_LARGE_GH_IMPORT_GH_TOKEN'] || Runtime::Env.github_access_token,
+ auto_paginate: true
+ )
+ end
+
+ let(:gh_branches) { github_client.branches(github_repo).map(&:name) }
+ let(:gh_commits) { github_client.commits(github_repo).map(&:sha) }
+ let(:gh_repo) { github_client.repository(github_repo) }
+
+ let(:gh_labels) do
+ github_client.labels(github_repo).map { |label| { name: label.name, color: "##{label.color}" } }
+ end
+
+ let(:gh_milestones) do
+ github_client
+ .list_milestones(github_repo, state: 'all')
+ .map { |ms| { title: ms.title, description: ms.description } }
+ end
+
+ let(:gh_all_issues) do
+ github_client.list_issues(github_repo, state: 'all')
+ end
+
+ let(:gh_prs) do
+ gh_all_issues.select(&:pull_request).each_with_object({}) do |pr, hash|
+ hash[pr.title] = {
+ body: pr.body || '',
+ comments: [*gh_pr_comments[pr.html_url], *gh_issue_comments[pr.html_url]].compact.sort
+ }
+ end
+ end
+
+ let(:gh_issues) do
+ gh_all_issues.reject(&:pull_request).each_with_object({}) do |issue, hash|
+ hash[issue.title] = {
+ body: issue.body || '',
+ comments: gh_issue_comments[issue.html_url]
+ }
+ end
+ end
+
+ let(:gh_issue_comments) do
+ github_client.issues_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ end
+ end
+
+ let(:gh_pr_comments) do
+ github_client.pull_requests_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ end
+ end
+
+ let(:imported_project) do
+ Resource::ProjectImportedFromGithub.fabricate_via_api! do |project|
+ project.add_name_uuid = false
+ project.name = 'imported-project'
+ project.group = group
+ project.github_personal_access_token = Runtime::Env.github_access_token
+ project.github_repository_path = github_repo
+ project.api_client = api_client
+ end
+ end
+
+ before do
+ group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+ end
+
+ after do |example|
+ user.remove_via_api!
+ next unless defined?(@import_time)
+
+ # save data for comparison after run finished
+ save_json(
+ "data",
+ {
+ import_time: @import_time,
+ github: {
+ project_name: github_repo,
+ branches: gh_branches,
+ commits: gh_commits,
+ labels: gh_labels,
+ milestones: gh_milestones,
+ prs: gh_prs,
+ issues: gh_issues
+ },
+ gitlab: {
+ project_name: imported_project.path_with_namespace,
+ branches: gl_branches,
+ commits: gl_commits,
+ labels: gl_labels,
+ milestones: gl_milestones,
+ mrs: mrs,
+ issues: gl_issues
+ }
+ }.to_json
+ )
+ end
+
+ it 'imports large Github repo via api' do
+ start = Time.now
+
+ imported_project # import the project
+ fetch_github_objects # fetch all objects right after import has started
+
+ import_status = lambda do
+ imported_project.reload!.import_status.tap do |status|
+ raise "Import of '#{imported_project.name}' failed!" if status == 'failed'
+ end
+ end
+ expect(import_status).to eventually_eq('finished').within(max_duration: import_max_duration, sleep_interval: 30)
+ @import_time = Time.now - start
+
+ aggregate_failures do
+ verify_repository_import
+ verify_labels_import
+ verify_milestones_import
+ verify_merge_requests_import
+ verify_issues_import
+ end
+ end
+
+ # Persist all objects from repository being imported
+ #
+ # @return [void]
+ def fetch_github_objects
+ logger.debug("== Fetching objects for github repo: '#{github_repo}' ==")
+
+ gh_repo
+ gh_branches
+ gh_commits
+ gh_prs
+ gh_issues
+ gh_labels
+ gh_milestones
+ end
+
+ # Verify repository imported correctly
+ #
+ # @return [void]
+ def verify_repository_import
+ logger.debug("== Verifying repository import ==")
+ expect(imported_project.description).to eq(gh_repo.description)
+ # check via include, importer creates more branches
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/332711
+ expect(gl_branches).to include(*gh_branches)
+ expect(gl_commits).to match_array(gh_commits)
+ end
+
+ # Verify imported merge requests and mr issues
+ #
+ # @return [void]
+ def verify_merge_requests_import
+ logger.debug("== Verifying merge request import ==")
+ verify_mrs_or_issues('mr')
+ end
+
+ # Verify imported issues and issue comments
+ #
+ # @return [void]
+ def verify_issues_import
+ logger.debug("== Verifying issue import ==")
+ verify_mrs_or_issues('issue')
+ end
+
+ # Verify imported labels
+ #
+ # @return [void]
+ def verify_labels_import
+ logger.debug("== Verifying label import ==")
+ # check via include, additional labels can be inherited from parent group
+ expect(gl_labels).to include(*gh_labels)
+ end
+
+ # Verify milestones import
+ #
+ # @return [void]
+ def verify_milestones_import
+ logger.debug("== Verifying milestones import ==")
+ expect(gl_milestones).to match_array(gh_milestones)
+ end
+
+ private
+
+ # Verify imported mrs or issues
+ #
+ # @param [String] type verification object, 'mrs' or 'issues'
+ # @return [void]
+ def verify_mrs_or_issues(type)
+ msg = ->(title) { "expected #{type} with title '#{title}' to have" }
+ expected = type == 'mr' ? mrs : gl_issues
+ actual = type == 'mr' ? gh_prs : gh_issues
+
+ # Compare length to have easy to read overview how many objects are missing
+ expect(expected.length).to(
+ eq(actual.length),
+ "Expected to contain same amount of #{type}s. Expected: #{expected.length}, actual: #{actual.length}"
+ )
+ logger.debug("= Comparing #{type}s =")
+ actual.each do |title, actual_item|
+ print "." # indicate that it is still going but don't spam the output with newlines
+
+ expected_item = expected[title]
+
+ expect(expected_item).to be_truthy, "#{msg.call(title)} been imported"
+ next unless expected_item
+
+ expect(expected_item[:body]).to(
+ include(actual_item[:body]),
+ "#{msg.call(title)} same description. diff:\n#{differ.diff(expected_item[:body], actual_item[:body])}"
+ )
+ expect(expected_item[:comments].length).to(
+ eq(actual_item[:comments].length),
+ "#{msg.call(title)} same amount of comments"
+ )
+ expect(expected_item[:comments]).to match_array(actual_item[:comments])
+ end
+ puts # print newline after last print to make output pretty
+ end
+
+ # Imported project branches
+ #
+ # @return [Array]
+ def gl_branches
+ @gl_branches ||= begin
+ logger.debug("= Fetching branches =")
+ imported_project.repository_branches(auto_paginate: true).map { |b| b[:name] }
+ end
+ end
+
+ # Imported project commits
+ #
+ # @return [Array]
+ def gl_commits
+ @gl_commits ||= begin
+ logger.debug("= Fetching commits =")
+ imported_project.commits(auto_paginate: true, attempts: 2).map { |c| c[:id] }
+ end
+ end
+
+ # Imported project labels
+ #
+ # @return [Array]
+ def gl_labels
+ @gl_labels ||= begin
+ logger.debug("= Fetching labels =")
+ imported_project.labels(auto_paginate: true).map { |label| label.slice(:name, :color) }
+ end
+ end
+
+ # Imported project milestones
+ #
+ # @return [<Type>] <description>
+ def gl_milestones
+ @gl_milestones ||= begin
+ logger.debug("= Fetching milestones =")
+ imported_project.milestones(auto_paginate: true).map { |ms| ms.slice(:title, :description) }
+ end
+ end
+
+ # Imported project merge requests
+ #
+ # @return [Hash]
+ def mrs
+ @mrs ||= begin
+ logger.debug("= Fetching merge requests =")
+ imported_mrs = imported_project.merge_requests(auto_paginate: true, attempts: 2)
+ logger.debug("= Transforming merge request objects for comparison =")
+ imported_mrs.each_with_object({}) do |mr, hash|
+ resource = Resource::MergeRequest.init do |resource|
+ resource.project = imported_project
+ resource.iid = mr[:iid]
+ resource.api_client = api_client
+ end
+
+ hash[mr[:title]] = {
+ body: mr[:description],
+ comments: resource.comments(auto_paginate: true, attempts: 2)
+ # remove system notes
+ .reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
+ .map { |c| sanitize(c[:body]) }
+ }
+ end
+ end
+ end
+
+ # Imported project issues
+ #
+ # @return [Hash]
+ def gl_issues
+ @gl_issues ||= begin
+ logger.debug("= Fetching issues =")
+ imported_issues = imported_project.issues(auto_paginate: true, attempts: 2)
+ logger.debug("= Transforming issue objects for comparison =")
+ imported_issues.each_with_object({}) do |issue, hash|
+ resource = Resource::Issue.init do |issue_resource|
+ issue_resource.project = imported_project
+ issue_resource.iid = issue[:iid]
+ issue_resource.api_client = api_client
+ end
+
+ hash[issue[:title]] = {
+ body: issue[:description],
+ comments: resource.comments(auto_paginate: true, attempts: 2).map { |c| sanitize(c[:body]) }
+ }
+ end
+ end
+ end
+
+ # Remove added prefixes by importer
+ #
+ # @param [String] body
+ # @return [String]
+ def sanitize(body)
+ body.gsub(/\*Created by: \S+\*\n\n/, "")
+ end
+
+ # Save json as file
+ #
+ # @param [String] name
+ # @param [String] json
+ # @return [void]
+ def save_json(name, json)
+ File.open("tmp/#{name}.json", "w") { |file| file.write(json) }
+ end
+ end
+ end
+end
+# rubocop:enable Rails/Pluck
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb
index 799efc243d4..9ac27a2ca06 100644
--- a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb
@@ -25,18 +25,6 @@ module QA
push.file_content = "Target branch test target branch #{SecureRandom.hex(8)}"
end
- # Confirm the target branch can be checked out to avoid a race condition
- # where the subsequent push option attempts to create an MR before the target branch is ready.
- Support::Retrier.retry_on_exception(sleep_interval: 5) do
- Git::Repository.perform do |repository|
- repository.uri = project.repository_http_location.uri
- repository.use_default_credentials
- repository.clone
- repository.configure_identity('GitLab QA', 'root@gitlab.com')
- repository.checkout(target_branch)
- end
- end
-
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.branch_name = "push-options-test-#{SecureRandom.hex(8)}"
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index 4bd99b4820e..c65d981d99a 100644
--- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -47,7 +47,7 @@ module QA
def create_project(user, api_client, project_name)
project = Resource::Project.fabricate_via_api! do |project|
- project.standalone = true
+ project.personal_namespace = true
project.add_name_uuid = false
project.name = project_name
project.path_with_namespace = "#{user.username}/#{project_name}"
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
index 5003d49fe6c..f79a3ebbe03 100644
--- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb
+++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb
@@ -3,7 +3,7 @@
require 'airborne'
module QA
- RSpec.describe 'Package', only: { subdomain: :staging } do
+ RSpec.describe 'Package', only: { subdomain: %i[staging pre] } do
include Support::Api
describe 'Container Registry' do
@@ -13,6 +13,7 @@ module QA
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-registry-api'
project.template_name = 'express'
+ project.api_client = api_client
end
end
@@ -37,6 +38,12 @@ module QA
- docker:19.03.12-dind
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ DOCKER_HOST: tcp://docker:2376
+ DOCKER_TLS_CERTDIR: "/certs"
+ DOCKER_TLS_VERIFY: 1
+ DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
+ before_script:
+ - until docker info; do sleep 1; done
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
@@ -50,6 +57,7 @@ module QA
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')
+ - echo $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"'
@@ -57,7 +65,6 @@ module QA
- '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
@@ -67,8 +74,9 @@ module QA
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.api_client = api_client
commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.project = project
commit.add_files([{
file_path: '.gitlab-ci.yml',
content: gitlab_ci_yaml
@@ -77,7 +85,7 @@ module QA
Support::Waiter.wait_until(max_duration: 10) { pipeline_is_triggered? }
- Support::Retrier.retry_until(max_duration: 260, sleep_interval: 5) do
+ Support::Retrier.retry_until(max_duration: 300, sleep_interval: 5) do
latest_pipeline_succeed?
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
index 6c2ff005f49..fe17b5c34e1 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
@@ -29,27 +29,11 @@ module QA
end
end
- let(:subgroup) do
- Resource::Group.fabricate_via_api! do |group|
- group.api_client = api_client
- group.sandbox = source_group
- group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
- end
- end
-
let(:imported_group) do
- Resource::Group.init do |group|
+ Resource::BulkImportGroup.init do |group|
group.api_client = api_client
group.sandbox = sandbox
- group.path = source_group.path
- end
- end
-
- let(:imported_subgroup) do
- Resource::Group.init do |group|
- group.api_client = api_client
- group.sandbox = imported_group
- group.path = subgroup.path
+ group.source_group_path = source_group.path
end
end
@@ -61,7 +45,6 @@ module QA
# create groups explicitly before connecting gitlab instance
source_group
- subgroup
Flow::Login.sign_in(as: user)
Page::Main::Menu.perform(&:go_to_create_group)
@@ -74,33 +57,15 @@ module QA
# Non blocking issues:
# https://gitlab.com/gitlab-org/gitlab/-/issues/331252
# https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back
- it(
- 'imports group with subgroups and labels',
- testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785'
- ) do
- Resource::GroupLabel.fabricate_via_api! do |label|
- label.api_client = api_client
- label.group = source_group
- label.title = "source-group-#{SecureRandom.hex(4)}"
- end
- Resource::GroupLabel.fabricate_via_api! do |label|
- label.api_client = api_client
- label.group = subgroup
- label.title = "subgroup-#{SecureRandom.hex(4)}"
- end
-
+ it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785' do
Page::Group::BulkImport.perform do |import_page|
- import_page.import_group(source_group.path, sandbox.path)
-
- expect(import_page).to have_imported_group(source_group.path, wait: 180)
+ import_page.import_group(imported_group.path, imported_group.sandbox.path)
- aggregate_failures do
- expect { imported_group.reload! }.to eventually_eq(source_group).within(duration: 10)
- expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10)
+ expect(import_page).to have_imported_group(imported_group.path, wait: 300)
- # Do not validate subgroups until https://gitlab.com/gitlab-org/gitlab/-/issues/332818 is resolved
- # expect { imported_subgroup.reload! }.to eventually_eq(subgroup).within(duration: 30)
- # expect { imported_subgroup.labels }.to eventually_include(*subgroup.labels).within(duration: 30)
+ imported_group.reload!.visit!
+ Page::Group::Show.perform do |group|
+ expect(group).to have_content(imported_group.path)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 4fffc786c14..564b14a872f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -2,24 +2,52 @@
module QA
RSpec.describe 'Manage', :smoke do
- describe 'Project creation' do
- it 'user creates a new project',
- testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1857' do
+ describe 'Project' do
+ shared_examples 'successful project creation' do
+ it 'creates a new project' do
+ Page::Project::Show.perform do |project|
+ expect(project).to have_content(project_name)
+ expect(project).to have_content(
+ /Project \S?#{project_name}\S+ was successfully created/
+ )
+ expect(project).to have_content('create awesome project test')
+ expect(project).to have_content('The repository for this project is empty')
+ end
+ end
+ end
+
+ before do
Flow::Login.sign_in
+ project
+ end
- created_project = Resource::Project.fabricate_via_browser_ui! do |project|
- project.name = 'awesome-project'
- project.description = 'create awesome project test'
+ context 'in group', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1857' do
+ let(:project_name) { "project-in-group-#{SecureRandom.hex(8)}" }
+ let(:project) do
+ Resource::Project.fabricate_via_browser_ui! do |project|
+ project.name = project_name
+ project.description = 'create awesome project test'
+ end
end
- Page::Project::Show.perform do |project|
- expect(project).to have_content(created_project.name)
- expect(project).to have_content(
- /Project \S?awesome-project\S+ was successfully created/
- )
- expect(project).to have_content('create awesome project test')
- expect(project).to have_content('The repository for this project is empty')
+ it_behaves_like 'successful project creation'
+ end
+
+ context 'in personal namespace', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1888' do
+ let(:project_name) { "project-in-personal-namespace-#{SecureRandom.hex(8)}" }
+ let(:project) do
+ Resource::Project.fabricate_via_browser_ui! do |project|
+ project.name = project_name
+ project.description = 'create awesome project test'
+ project.personal_namespace = true
+ end
end
+
+ it_behaves_like 'successful project creation'
+ end
+
+ after do
+ project.remove_via_api!
end
end
end
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 4f85fa257a2..c55ecb28361 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
@@ -3,9 +3,11 @@
module QA
RSpec.describe 'Manage', :github, :requires_admin do
describe 'Project import' do
- let!(:api_client) { Runtime::API::Client.as_admin }
- let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } }
- let!(:user) do
+ let(:github_repo) { 'gitlab-qa-github/test-project' }
+ let(:imported_project_name) { 'imported-project' }
+ let(:api_client) { Runtime::API::Client.as_admin }
+ let(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } }
+ let(:user) do
Resource::User.fabricate_via_api! do |resource|
resource.api_client = api_client
resource.hard_delete_on_api_removal = true
@@ -13,16 +15,25 @@ module QA
end
let(:imported_project) do
- Resource::ProjectImportedFromGithub.fabricate_via_browser_ui! do |project|
- project.name = 'imported-project'
+ Resource::ProjectImportedFromGithub.init do |project|
+ project.import = true
+ project.add_name_uuid = false
+ project.name = imported_project_name
project.group = group
project.github_personal_access_token = Runtime::Env.github_access_token
- project.github_repository_path = 'gitlab-qa-github/test-project'
+ project.github_repository_path = github_repo
end
end
before do
group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+
+ Flow::Login.sign_in(as: user)
+ Page::Main::Menu.perform(&:go_to_create_project)
+ Page::Project::New.perform do |project_page|
+ project_page.click_import_project
+ project_page.click_github_link
+ end
end
after do
@@ -30,13 +41,24 @@ module QA
end
it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1762' do
- Flow::Login.sign_in(as: user)
+ Page::Project::Import::Github.perform do |import_page|
+ import_page.add_personal_access_token(Runtime::Env.github_access_token)
+ import_page.import!(github_repo, group.full_path, imported_project_name)
- imported_project # import the project
+ aggregate_failures do
+ expect(import_page).to have_imported_project(github_repo)
+ # validate button is present instead of navigating to avoid dealing with multiple tabs
+ # which makes the test more complicated
+ expect(import_page).to have_go_to_project_button(github_repo)
+ end
+ end
+ imported_project.reload!.visit!
Page::Project::Show.perform do |project|
- expect(project).to have_content(imported_project.name)
- expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.')
+ aggregate_failures do
+ expect(project).to have_content(imported_project_name)
+ expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.')
+ end
end
end
end
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 59d34612ca7..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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331978', type: :bug } do
+ RSpec.describe 'Create' do
context 'Design Management' do
let(:design) do
Resource::Design.fabricate! do |design|
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
index d11afde5648..37008e6d507 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
@@ -15,7 +15,7 @@ module QA
merge_request.visit!
end
- it 'views the merge request email patches', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1689' do
+ it 'views the merge request email patches', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1689' do
Page::MergeRequest::Show.perform(&:view_email_patches)
expect(page.text).to start_with('From')
@@ -23,10 +23,11 @@ module QA
expect(page).to have_content("diff --git a/#{merge_request.file_name} b/#{merge_request.file_name}")
end
- it 'views the merge request plain diff', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/417' do
+ it 'views the merge request plain diff', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/417' do
Page::MergeRequest::Show.perform(&:view_plain_diff)
- expect(page.text).to start_with("diff --git a/#{merge_request.file_name} b/#{merge_request.file_name}")
+ expect(page.text).to start_with('diff')
+ expect(page).to have_content("diff --git a/#{merge_request.file_name} b/#{merge_request.file_name}")
expect(page).to have_content('+File Added')
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
new file mode 100644
index 00000000000..2a46604f8ac
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Content Editor' do
+ let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+ let(:page_title) { 'Content Editor Page' }
+ let(:heading_text) { 'My New Heading' }
+ let(:image_file_name) { 'testfile.png' }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ initial_wiki.project.remove_via_api!
+ end
+
+ it 'creates a formatted Wiki page with an image uploaded', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1861' do
+ initial_wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_new_page)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title(page_title)
+ edit.use_new_editor
+ edit.add_heading('Heading 1', heading_text)
+ edit.upload_image(File.absolute_path(File.join('qa', 'fixtures', 'designs', image_file_name)))
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_submit)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ aggregate_failures 'page shows expected content' do
+ expect(wiki).to have_title(page_title)
+ expect(wiki).to have_heading('h1', heading_text)
+ expect(wiki).to have_image(image_file_name)
+ end
+ end
+ end
+ end
+ end
+end
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
index 5083b7b0859..5b3949f9c3a 100644
--- 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
@@ -1,10 +1,8 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :requires_admin do
+ RSpec.describe 'Verify' 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'
@@ -12,7 +10,6 @@ module QA
end
before do
- Runtime::Feature.enable(feature_flag)
Flow::Login.sign_in
add_files_to_project
project.visit!
@@ -20,7 +17,6 @@ module QA
end
after do
- Runtime::Feature.disable(feature_flag)
project.remove_via_api!
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 01aada2d6dd..b43581289ef 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
@@ -73,7 +73,7 @@ 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|
# 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.has_pipeline_status?('waiting for manual action')
show.merge!
expect(show).to be_merged
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb
new file mode 100644
index 00000000000..47b36b55c8c
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'faker'
+
+module QA
+ RSpec.describe 'Verify', :runner do
+ context 'When job is configured to only run on merge_request_events' do
+ let(:mr_only_job_name) { 'mr_only_job' }
+ let(:non_mr_only_job_name) { 'non_mr_only_job' }
+ let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-only-job'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = executor
+ runner.tags = [executor]
+ end
+ end
+
+ let!(:ci_file) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ #{mr_only_job_name}:
+ script: echo 'OK'
+ rules:
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
+ #{non_mr_only_job_name}:
+ script: echo 'OK'
+ rules:
+ - if: '$CI_PIPELINE_SOURCE != "merge_request_event"'
+ YAML
+ }
+ ]
+ )
+ end
+ end
+
+ let(:merge_request) do
+ Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.project = project
+ merge_request.description = Faker::Lorem.sentence
+ merge_request.target_new_branch = false
+ merge_request.file_name = 'new.txt'
+ merge_request.file_content = Faker::Lorem.sentence
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ merge_request.visit!
+ Page::MergeRequest::Show.perform(&:click_pipeline_link)
+ end
+
+ after do
+ runner.remove_via_api!
+ project.remove_via_api!
+ end
+
+ it 'only runs the job configured to run on merge requests', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/170' do
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ aggregate_failures do
+ expect(pipeline).to have_job(mr_only_job_name)
+ expect(pipeline).to have_no_job(non_mr_only_job_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
index c1625f1e679..adacedb36ab 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
@@ -41,7 +41,7 @@ module QA
after do
runner.remove_via_api!
- group.remove_via_api!
+ [upstream_project, downstream_project].each(&:remove_via_api!)
end
it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1086' do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
index 07484feb686..7d3f8f2b1d4 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
@@ -39,9 +39,7 @@ module QA
merge_request.visit!
Page::MergeRequest::Show.perform do |mr_widget|
- Support::Retrier.retry_until(max_attempts: 5, sleep_interval: 5) do
- mr_widget.has_pipeline_status?('passed')
- end
+ mr_widget.has_pipeline_status?('passed')
expect(mr_widget).to have_content('Test coverage 66.67%')
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb
index 4b7669810ec..375a371c2b1 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :registry, :orchestrated do
+ RSpec.describe 'Package', :registry, :orchestrated, only: { pipeline: :main } do
describe 'Self-managed Container Registry' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb
new file mode 100644
index 00000000000..be1d0dd8e81
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Package', :orchestrated, :registry do
+ describe 'Dependency Proxy' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'dependency-proxy-project'
+ project.visibility = :private
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.name = "qa-runner-#{Time.now.to_i}"
+ runner.tags = ["runner-for-#{project.name}"]
+ runner.executor = :docker
+ runner.project = project
+ end
+ end
+
+ let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) }
+ let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" }
+ let(:dependency_proxy_url) { "#{gitlab_host_with_port}/#{project.group.full_path}/dependency_proxy/containers" }
+
+ before do
+ Flow::Login.sign_in
+
+ project.group.visit!
+
+ Page::Group::Menu.perform(&:go_to_dependency_proxy)
+
+ Page::Group::DependencyProxy.perform do |index|
+ expect(index).to have_dependency_proxy_enabled
+ end
+ end
+
+ after do
+ runner.remove_via_api!
+ end
+
+ where(:docker_client_version) do
+ %w[docker:19.03.12 docker:20.10]
+ end
+
+ with_them do
+ it "pulls an image using the dependency proxy", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1862' do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files([{
+ file_path: '.gitlab-ci.yml',
+ content:
+ <<~YAML
+ dependency-proxy-pull-test:
+ image: "#{docker_client_version}"
+ services:
+ - name: "#{docker_client_version}-dind"
+ command:
+ - /bin/sh
+ - -c
+ - |
+ apk add --no-cache openssl
+ true | openssl s_client -showcerts -connect gitlab.test:5050 > /usr/local/share/ca-certificates/gitlab.test.crt
+ update-ca-certificates
+ dockerd-entrypoint.sh || exit
+ before_script:
+ - apk add curl jq grep
+ - docker login -u "$CI_DEPENDENCY_PROXY_USER" -p "$CI_DEPENDENCY_PROXY_PASSWORD" "$CI_DEPENDENCY_PROXY_SERVER"
+ script:
+ - docker pull #{dependency_proxy_url}/alpine:latest
+ - TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq --raw-output .token)
+ - 'curl --head --header "Authorization: Bearer $TOKEN" "https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest" 2>&1'
+ - docker pull #{dependency_proxy_url}/alpine:latest
+ - 'curl --head --header "Authorization: Bearer $TOKEN" "https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest" 2>&1'
+ tags:
+ - "runner-for-#{project.name}"
+ YAML
+ }])
+ end
+
+ project.visit!
+ Flow::Pipeline.visit_latest_pipeline
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('dependency-proxy-pull-test')
+ end
+
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
+
+ project.group.visit!
+
+ Page::Group::Menu.perform(&:go_to_dependency_proxy)
+
+ Page::Group::DependencyProxy.perform do |index|
+ expect(index).to have_blob_count("Contains 2 blobs of images")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb b/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb
index 65fc12545b7..8c686e65e33 100644
--- a/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Package' do
- describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] } do
+ describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/337791', type: :waiting_on } do
let(:group) { Resource::Group.fabricate_via_api! }
let(:imported_project) do
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 a9034174cab..7b924f1b52b 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
@@ -26,10 +26,10 @@ module QA
end
end
- let(:gitlab_address_with_port) do
- uri = URI.parse(Runtime::Scenario.gitlab_address)
- "#{uri.scheme}://#{uri.host}:#{uri.port}"
- end
+ let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) }
+ let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" }
+ let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" }
+ let(:personal_access_token) { Runtime::Env.personal_access_token }
before do
Flow::Login.sign_in
@@ -42,14 +42,25 @@ module QA
content:
<<~YAML
image: python:latest
+ stages:
+ - run
+ - install
run:
+ stage: run
script:
- pip install twine
- python setup.py sdist bdist_wheel
- "TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*"
tags:
- "runner-for-#{project.name}"
+ install:
+ stage: install
+ script:
+ - "pip install mypypipackage --no-deps --index-url #{uri.scheme}://#{personal_access_token}:#{personal_access_token}@#{gitlab_host_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple --trusted-host #{gitlab_host_with_port}"
+ tags:
+ - "runner-for-#{project.name}"
+
YAML
},
{
@@ -87,6 +98,16 @@ module QA
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 800)
end
+
+ Flow::Pipeline.visit_latest_pipeline
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('install')
+ end
+
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
end
after do
@@ -95,20 +116,22 @@ module QA
project&.remove_via_api!
end
- it 'publishes a pypi package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1087' do
- Page::Project::Menu.perform(&:click_packages_link)
+ context 'when at the project level' do
+ it 'publishes and installs a pypi package and deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1087' 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)
- end
+ Page::Project::Packages::Index.perform do |index|
+ expect(index).to have_package(package.name)
+ index.click_package(package.name)
+ end
- Page::Project::Packages::Show.perform(&:click_delete)
+ Page::Project::Packages::Show.perform(&:click_delete)
- 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)
+ 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)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 8e61ec4d2d7..f4a5c715684 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -5,27 +5,31 @@ require 'digest/sha1'
module QA
RSpec.describe 'Release', :runner do
describe 'Git clone using a deploy key' do
- before do
- Flow::Login.sign_in
-
- @runner_name = "qa-runner-#{Time.now.to_i}"
+ let(:runner_name) { "qa-runner-#{SecureRandom.hex(4)}" }
+ let(:repository_location) { project.repository_ssh_location }
- @project = Resource::Project.fabricate_via_api! do |project|
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
project.name = 'deploy-key-clone-project'
end
+ end
- @repository_location = @project.repository_ssh_location
-
- @runner = Resource::Runner.fabricate_via_api! do |resource|
- resource.project = @project
- resource.name = @runner_name
- resource.tags = %w[qa docker]
+ let!(:runner) do
+ Resource::Runner.fabricate_via_api! do |resource|
+ resource.project = project
+ resource.name = runner_name
+ resource.tags = [runner_name]
resource.image = 'gitlab/gitlab-runner:alpine'
end
end
+ before do
+ Flow::Login.sign_in
+ end
+
after do
- @runner.remove_via_api!
+ runner.remove_via_api!
+ project.remove_via_api!
end
keys = [
@@ -39,7 +43,7 @@ module QA
key = key_class.new(*bits)
Resource::DeployKey.fabricate_via_browser_ui! do |resource|
- resource.project = @project
+ resource.project = project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
end
@@ -49,25 +53,23 @@ module QA
make_ci_variable(deploy_key_name, key)
gitlab_ci = <<~YAML
- cat-config:
- script:
- - apk add --update --no-cache openssh-client
- - mkdir -p ~/.ssh
- - ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts
- - eval $(ssh-agent -s)
- - ssh-add -D
- - echo "$#{deploy_key_name}" | ssh-add -
- - git clone #{@repository_location.git_uri}
- - cd #{@project.name}
- - git checkout #{deploy_key_name}
- - sha1sum .gitlab-ci.yml
- tags:
- - qa
- - docker
+ cat-config:
+ script:
+ - which ssh-agent || ( apk --update add openssh-client )
+ - mkdir -p ~/.ssh
+ - ssh-keyscan -p #{repository_location.port} #{repository_location.host} >> ~/.ssh/known_hosts
+ - eval $(ssh-agent -s)
+ - ssh-add -D
+ - echo "$#{deploy_key_name}" | ssh-add -
+ - git clone #{repository_location.git_uri}
+ - cd #{project.name}
+ - git checkout #{deploy_key_name}
+ - sha1sum .gitlab-ci.yml
+ tags: [#{runner_name}]
YAML
Resource::Repository::ProjectPush.fabricate! do |resource|
- resource.project = @project
+ resource.project = project
resource.file_name = '.gitlab-ci.yml'
resource.commit_message = 'Add .gitlab-ci.yml'
resource.file_content = gitlab_ci
@@ -81,8 +83,10 @@ module QA
Page::Project::Pipeline::Show.perform(&:click_on_first_job)
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful
- expect(job.output).to include(sha1sum)
+ aggregate_failures 'job succeeds and has correct sha1sum' do
+ expect(job).to be_successful
+ expect(job.output).to include(sha1sum)
+ end
end
end
@@ -90,7 +94,7 @@ module QA
def make_ci_variable(key_name, key)
Resource::CiVariable.fabricate_via_api! do |resource|
- resource.project = @project
+ resource.project = project
resource.key = key_name
resource.value = key.private_key
resource.masked = false
diff --git a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
index 1803b4b16de..3a59efe645a 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Configure' do
- describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225315', type: :flaky } do
+ RSpec.describe 'Configure', except: { job: 'review-qa-*' } do
+ describe 'Kubernetes Cluster Integration', :requires_admin, :skip_live_env, :smoke do
context 'Project Clusters' do
let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do
@@ -20,7 +20,7 @@ module QA
cluster.remove!
end
- it 'can create and associate a project cluster', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/707' do
+ it 'can create and associate a project cluster', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/707' do
Resource::KubernetesCluster::ProjectCluster.fabricate_via_browser_ui! do |k8s_cluster|
k8s_cluster.project = project
k8s_cluster.cluster = cluster
diff --git a/qa/qa/specs/helpers/context_formatter.rb b/qa/qa/specs/helpers/context_formatter.rb
new file mode 100644
index 00000000000..26db7c3b67e
--- /dev/null
+++ b/qa/qa/specs/helpers/context_formatter.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+require "rspec/core/formatters/base_formatter"
+
+module QA
+ module Specs
+ module Helpers
+ class ContextFormatter < ::RSpec::Core::Formatters::BaseFormatter
+ include ContextSelector
+
+ ::RSpec::Core::Formatters.register(
+ self,
+ :example_group_started,
+ :example_started
+ )
+
+ # Starts example group
+ # @param [RSpec::Core::Notifications::GroupNotification] example_group_notification
+ # @return [void]
+ def example_group_started(example_group_notification)
+ set_skip_metadata(example_group_notification.group)
+ end
+
+ # Starts example
+ # @param [RSpec::Core::Notifications::ExampleNotification] example_notification
+ # @return [void]
+ def example_started(example_notification)
+ example = example_notification.example
+
+ # if skip propagated from example_group, do not reset skip metadata
+ set_skip_metadata(example_notification.example) unless example.metadata[:skip]
+ end
+
+ private
+
+ # Skip example_group or example
+ #
+ # @param [<RSpec::Core::ExampleGroup, RSpec::Core::Example>] example
+ # @return [void]
+ def set_skip_metadata(example)
+ return skip_only(example.metadata) if example.metadata.key?(:only)
+ return skip_except(example.metadata) if example.metadata.key?(:except)
+ end
+
+ # Skip based on 'only' condition
+ #
+ # @param [Hash] metadata
+ # @return [void]
+ def skip_only(metadata)
+ return if context_matches?(metadata[:only])
+
+ metadata[:skip] = 'Test is not compatible with this environment or pipeline'
+ end
+
+ # Skip based on 'except' condition
+ #
+ # @param [Hash] metadata
+ # @return [void]
+ def skip_except(metadata)
+ return unless except?(metadata[:except])
+
+ metadata[:skip] = 'Test is excluded in this job'
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/helpers/context_selector.rb b/qa/qa/specs/helpers/context_selector.rb
index 40ecb9b3506..57665babf68 100644
--- a/qa/qa/specs/helpers/context_selector.rb
+++ b/qa/qa/specs/helpers/context_selector.rb
@@ -8,18 +8,6 @@ module QA
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?(:except)
- skip('Test is excluded in this job') if ContextSelector.except?(example.metadata[:except])
- end
- end
- end
- end
-
def except?(*options)
return false if Runtime::Env.ci_job_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:job].present? }
return false if Runtime::Env.ci_project_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:pipeline].present? }
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
index 15b4ed8336b..49d91fc87cd 100644
--- a/qa/qa/specs/helpers/quarantine.rb
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -10,26 +10,6 @@ module QA
extend self
- def configure_rspec
- ::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)
- end
- end
- end
-
- # Skip the entire context if a context is quarantined. This avoids running
- # before blocks unnecessarily.
- def skip_or_run_quarantined_contexts(filters, example)
- return unless example.metadata.key?(:quarantine)
-
- skip_or_run_quarantined_tests_or_contexts(filters, example)
- end
-
# Skip tests in quarantine unless we explicitly focus on them.
def skip_or_run_quarantined_tests_or_contexts(filters, example)
if filters.key?(:quarantine)
@@ -43,19 +23,19 @@ module QA
# running that ldap test as well because of the :quarantine metadata.
# We could use an exclusion filter, but this way the test report will list
# the quarantined tests when they're not run so that we're aware of them
- skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
- else
- if example.metadata.key?(:quarantine)
- quarantine_tag = example.metadata[:quarantine]
-
- 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 ContextSelector.context_matches?(quarantine_tag[:only])
- end
+ if should_skip_when_focused?(example.metadata, included_filters)
+ example.metadata[:skip] = "Only running tests tagged with :quarantine and any of #{included_filters.keys}"
+ end
+ elsif example.metadata.key?(:quarantine)
+ quarantine_tag = example.metadata[:quarantine]
- skip(quarantine_message(quarantine_tag))
+ if quarantine_tag.is_a?(Hash) && quarantine_tag&.key?(:only) && !ContextSelector.context_matches?(quarantine_tag[: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
end
+
+ example.metadata[:skip] = quarantine_message(quarantine_tag)
end
end
@@ -64,7 +44,7 @@ module QA
end
def quarantine_message(quarantine_tag)
- quarantine_message = %w(In quarantine)
+ quarantine_message = %w[In quarantine]
quarantine_message << case quarantine_tag
when String
": #{quarantine_tag}"
diff --git a/qa/qa/specs/helpers/quarantine_formatter.rb b/qa/qa/specs/helpers/quarantine_formatter.rb
new file mode 100644
index 00000000000..c42debee07c
--- /dev/null
+++ b/qa/qa/specs/helpers/quarantine_formatter.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+require "rspec/core/formatters/base_formatter"
+
+module QA
+ module Specs
+ module Helpers
+ class QuarantineFormatter < ::RSpec::Core::Formatters::BaseFormatter
+ include Quarantine
+
+ ::RSpec::Core::Formatters.register(
+ self,
+ :example_group_started,
+ :example_started
+ )
+
+ # Starts example group
+ # @param [RSpec::Core::Notifications::GroupNotification] example_group_notification
+ # @return [void]
+ def example_group_started(example_group_notification)
+ group = example_group_notification.group
+
+ skip_or_run_quarantined_tests_or_contexts(filters, group)
+ end
+
+ # Starts example
+ # @param [RSpec::Core::Notifications::ExampleNotification] example_notification
+ # @return [void]
+ def example_started(example_notification)
+ example = example_notification.example
+
+ # if skip propagated from example_group, do not reset skip metadata
+ skip_or_run_quarantined_tests_or_contexts(filters, example) unless example.metadata[:skip]
+ end
+
+ private
+
+ def filters
+ @filters ||= ::RSpec.configuration.inclusion_filter.rules
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/helpers/rspec.rb b/qa/qa/specs/helpers/rspec.rb
index f49e556b0d9..853dfbfd1b6 100644
--- a/qa/qa/specs/helpers/rspec.rb
+++ b/qa/qa/specs/helpers/rspec.rb
@@ -19,8 +19,10 @@ module QA
# expanding into the global state
# See: https://github.com/rspec/rspec-core/issues/2603
def describe_successfully(*args, &describe_body)
+ reporter = ::RSpec.configuration.reporter
+
example_group = RSpec.describe(*args, &describe_body)
- ran_successfully = example_group.run RaiseOnFailuresReporter
+ ran_successfully = example_group.run reporter
expect(ran_successfully).to eq true
example_group
end
diff --git a/qa/qa/support/allure_metadata_formatter.rb b/qa/qa/support/allure_metadata_formatter.rb
new file mode 100644
index 00000000000..8a18eeca839
--- /dev/null
+++ b/qa/qa/support/allure_metadata_formatter.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+require "rspec/core/formatters/base_formatter"
+
+module QA
+ module Support
+ class AllureMetadataFormatter < ::RSpec::Core::Formatters::BaseFormatter
+ ::RSpec::Core::Formatters.register(
+ self,
+ :example_started
+ )
+
+ # Starts example
+ # @param [RSpec::Core::Notifications::ExampleNotification] example_notification
+ # @return [void]
+ def example_started(example_notification)
+ example = example_notification.example
+
+ testcase = example.metadata[:testcase]
+ example.tms('Testcase', testcase) if testcase
+
+ quarantine_issue = example.metadata.dig(:quarantine, :issue)
+ example.issue('Quarantine issue', quarantine_issue) if quarantine_issue
+
+ spec_file = example.file_path.split('/').last
+ example.issue(
+ 'Failure issues',
+ "https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}"
+ )
+ return unless Runtime::Env.running_in_ci?
+
+ example.add_link(name: "Job(#{Runtime::Env.ci_job_name})", url: Runtime::Env.ci_job_url)
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index de9da3171b0..579227b4f7a 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -79,11 +79,31 @@ module QA
error.response
end
- def with_paginated_response_body(url)
+ def auto_paginated_response(url, attempts: 0)
+ pages = []
+ with_paginated_response_body(url, attempts: attempts) { |response| pages << response }
+
+ pages.flatten
+ end
+
+ def with_paginated_response_body(url, attempts: 0)
+ not_ok_error = lambda do |resp|
+ raise "Failed to GET #{QA::Runtime::API::Request.masked_url(url)} - (#{resp.code}): `#{resp}`."
+ end
+
loop do
- response = get(url)
+ response = if attempts > 0
+ Retrier.retry_on_exception(max_attempts: attempts, log: false) do
+ get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
+ end
+ else
+ get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
+ end
+
+ page, pages = response.headers.values_at(:x_page, :x_total_pages)
+ api_endpoint = url.match(%r{v4/(\S+)\?})[1]
- QA::Runtime::Logger.debug("Fetching page #{response.headers[:x_page]} of #{response.headers[:x_total_pages]}...")
+ QA::Runtime::Logger.debug("Fetching page (#{page}/#{pages}) for '#{api_endpoint}' ...") unless pages.to_i <= 1
yield parse_body(response)
@@ -95,8 +115,11 @@ module QA
end
def pagination_links(response)
- response.headers[:link].split(',').map do |link|
- match = link.match(/\<(?<url>.*)\>\; rel=\"(?<rel>\w+)\"/)
+ link = response.headers[:link]
+ return unless link
+
+ link.split(',').map do |link|
+ match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/)
break nil unless match
{ url: match[:url], rel: match[:rel] }
diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb
index 6f8c4a59566..b3a2472d702 100644
--- a/qa/qa/support/repeater.rb
+++ b/qa/qa/support/repeater.rb
@@ -11,7 +11,15 @@ module QA
RetriesExceededError = Class.new(RepeaterConditionExceededError)
WaitExceededError = Class.new(RepeaterConditionExceededError)
- def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
+ def repeat_until(
+ max_attempts: nil,
+ max_duration: nil,
+ reload_page: nil,
+ sleep_interval: 0,
+ raise_on_failure: true,
+ retry_on_exception: false,
+ log: true
+ )
attempts = 0
start = Time.now
@@ -29,17 +37,19 @@ module QA
raise unless retry_on_exception
attempts += 1
- if remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
- sleep_and_reload_if_needed(sleep_interval, reload_page)
+ raise unless remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
- retry
- else
- raise
- end
+ sleep_and_reload_if_needed(sleep_interval, reload_page)
+ retry
end
if raise_on_failure
- raise RetriesExceededError, "Retry condition not met after #{max_attempts} #{'attempt'.pluralize(max_attempts)}" unless remaining_attempts?(attempts, max_attempts)
+ unless remaining_attempts?(attempts, max_attempts)
+ raise(
+ RetriesExceededError,
+ "Retry condition not met after #{max_attempts} #{'attempt'.pluralize(max_attempts)}"
+ )
+ end
raise WaitExceededError, "Wait condition not met after #{max_duration} #{'second'.pluralize(max_duration)}"
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
index 25dbb42cf6f..fde8ac263ca 100644
--- a/qa/qa/support/retrier.rb
+++ b/qa/qa/support/retrier.rb
@@ -7,21 +7,21 @@ module QA
module_function
- def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5)
- QA::Runtime::Logger.debug(
- <<~MSG.tr("\n", ' ')
- with retry_on_exception: max_attempts: #{max_attempts};
- reload_page: #{reload_page};
- sleep_interval: #{sleep_interval}
- MSG
- )
+ def retry_on_exception(max_attempts: 3, reload_page: nil, sleep_interval: 0.5, log: true)
+ if log
+ msg = ["with retry_on_exception: max_attempts: #{max_attempts}"]
+ msg << "reload_page: #{reload_page}" if reload_page
+ msg << "sleep_interval: #{sleep_interval}"
+ QA::Runtime::Logger.debug(msg.join('; '))
+ end
result = nil
repeat_until(
max_attempts: max_attempts,
reload_page: reload_page,
sleep_interval: sleep_interval,
- retry_on_exception: true
+ retry_on_exception: true,
+ log: log
) do
result = yield
@@ -29,7 +29,7 @@ module QA
# We set it to `true` so that it doesn't repeat if there's no exception
true
end
- QA::Runtime::Logger.debug("ended retry_on_exception")
+ QA::Runtime::Logger.debug("ended retry_on_exception") if log
result
end