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