summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-18 08:17:02 +0000
commitb39512ed755239198a9c294b6a45e65c05900235 (patch)
treed234a3efade1de67c46b9e5a38ce813627726aa7 /qa
parentd31474cf3b17ece37939d20082b07f6657cc79a9 (diff)
downloadgitlab-ce-b39512ed755239198a9c294b6a45e65c05900235.tar.gz
Add latest changes from gitlab-org/gitlab@15-3-stable-eev15.3.0-rc42
Diffstat (limited to 'qa')
-rw-r--r--qa/Dockerfile2
-rw-r--r--qa/Gemfile4
-rw-r--r--qa/Gemfile.lock19
-rw-r--r--qa/Rakefile7
-rw-r--r--qa/qa.rb12
-rw-r--r--qa/qa/ce/strategy.rb10
-rw-r--r--qa/qa/flow/purchase.rb2
-rw-r--r--qa/qa/page/admin/overview/users/components/impersonation_tokens.rb18
-rw-r--r--qa/qa/page/admin/overview/users/show.rb20
-rw-r--r--qa/qa/page/alert/auto_devops_alert.rb2
-rw-r--r--qa/qa/page/component/access_tokens.rb10
-rw-r--r--qa/qa/page/component/ci_badge_link.rb2
-rw-r--r--qa/qa/page/component/confirm_modal.rb11
-rw-r--r--qa/qa/page/component/groups_filter.rb4
-rw-r--r--qa/qa/page/component/issuable/sidebar.rb4
-rw-r--r--qa/qa/page/component/namespace_select.rb5
-rw-r--r--qa/qa/page/component/wiki_page_form.rb4
-rw-r--r--qa/qa/page/dashboard/groups.rb9
-rw-r--r--qa/qa/page/dashboard/projects.rb6
-rw-r--r--qa/qa/page/file/edit.rb24
-rw-r--r--qa/qa/page/file/shared/editor.rb6
-rw-r--r--qa/qa/page/group/new.rb6
-rw-r--r--qa/qa/page/issuable/new.rb19
-rw-r--r--qa/qa/page/label/index.rb10
-rw-r--r--qa/qa/page/label/new.rb12
-rw-r--r--qa/qa/page/main/login.rb10
-rw-r--r--qa/qa/page/merge_request/show.rb38
-rw-r--r--qa/qa/page/profile/ssh_keys.rb6
-rw-r--r--qa/qa/page/project/new.rb6
-rw-r--r--qa/qa/page/project/pipeline_editor/show.rb25
-rw-r--r--qa/qa/page/project/settings/advanced.rb6
-rw-r--r--qa/qa/page/project/settings/integrations.rb7
-rw-r--r--qa/qa/page/project/settings/pages.rb25
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb4
-rw-r--r--qa/qa/page/project/settings/services/jenkins.rb6
-rw-r--r--qa/qa/page/project/show.rb6
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb8
-rw-r--r--qa/qa/page/project/web_ide/edit.rb12
-rw-r--r--qa/qa/page/view.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb19
-rw-r--r--qa/qa/resource/group.rb11
-rw-r--r--qa/qa/resource/group_ci_variable.rb47
-rw-r--r--qa/qa/resource/impersonation_token.rb97
-rw-r--r--qa/qa/resource/integrations/project.rb25
-rw-r--r--qa/qa/resource/personal_access_token.rb23
-rw-r--r--qa/qa/resource/personal_access_token_cache.rb12
-rw-r--r--qa/qa/resource/project.rb32
-rw-r--r--qa/qa/resource/sandbox.rb7
-rw-r--r--qa/qa/runtime/env.rb12
-rw-r--r--qa/qa/runtime/ip_address.rb5
-rw-r--r--qa/qa/runtime/user.rb2
-rw-r--r--qa/qa/scenario/test/integration/metrics.rb13
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb7
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb3
-rw-r--r--qa/qa/service/docker_run/jenkins.rb43
-rw-r--r--qa/qa/service/praefect_manager.rb8
-rw-r--r--qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb6
-rw-r--r--qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb62
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb5
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb22
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb9
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb21
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/praefect_repo_sync_spec.rb1
-rw-r--r--qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb32
-rw-r--r--qa/qa/specs/features/api/5_package/container_registry_spec.rb8
-rw-r--r--qa/qa/specs/features/api/8_monitor/metrics_spec.rb50
-rw-r--r--qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb)2
-rw-r--r--qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_default_enabled_spec.rb)2
-rw-r--r--qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_disabled_spec.rb)2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb37
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/user/impersonation_token_spec.rb32
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb (renamed from qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb)7
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb (renamed from qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb)4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb (renamed from qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb)4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb177
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb (renamed from qa/qa/specs/features/browser_ui/6_release/pages/pages_pipeline_spec.rb)3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb (renamed from qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb)9
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb3
-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/source_editor/source_editor_toolbar_spec.rb55
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb (renamed from qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb)35
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb175
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb103
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--qa/qa/specs/features/sanity/feature_flags_spec.rb4
-rw-r--r--qa/qa/support/helpers/mask_token.rb10
-rw-r--r--qa/qa/support/knapsack_report.rb6
-rw-r--r--qa/qa/support/loglinking.rb16
-rw-r--r--qa/qa/support/parallel_pipeline_jobs.rb76
-rw-r--r--qa/qa/tools/delete_projects.rb16
-rw-r--r--qa/qa/tools/delete_user_projects.rb64
-rw-r--r--qa/qa/tools/lib/project.rb33
-rw-r--r--qa/qa/vendor/jenkins/README.md61
-rw-r--r--qa/qa/vendor/jenkins/client.rb298
-rw-r--r--qa/qa/vendor/jenkins/helpers.rb36
-rw-r--r--qa/qa/vendor/jenkins/job.rb166
-rw-r--r--qa/qa/vendor/jenkins/page/base.rb24
-rw-r--r--qa/qa/vendor/jenkins/page/configure.rb48
-rw-r--r--qa/qa/vendor/jenkins/page/configure_job.rb77
-rw-r--r--qa/qa/vendor/jenkins/page/last_job_console.rb44
-rw-r--r--qa/qa/vendor/jenkins/page/login.rb31
-rw-r--r--qa/qa/vendor/jenkins/page/new_credentials.rb50
-rw-r--r--qa/qa/vendor/jenkins/page/new_job.rb38
-rw-r--r--qa/spec/scenario/test/sanity/selectors_spec.rb20
-rw-r--r--qa/spec/service/docker_run/gitlab_runner_spec.rb52
-rw-r--r--qa/spec/support/loglinking_spec.rb34
-rw-r--r--qa/spec/support/page_error_checker_spec.rb5
-rw-r--r--qa/tasks/knapsack.rake25
-rw-r--r--qa/tasks/reliable_report.rake2
-rw-r--r--qa/tasks/vulnerabilities.rake2
125 files changed, 2164 insertions, 759 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 9611b3653eb..341732ab56f 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,5 +1,5 @@
ARG DOCKER_VERSION=20.10.14
-ARG CHROME_VERSION=101
+ARG CHROME_VERSION=103
ARG QA_BUILD_TARGET=qa
FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS qa
diff --git a/qa/Gemfile b/qa/Gemfile
index d8d00400563..7c46d35bb48 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -29,12 +29,16 @@ gem 'influxdb-client', '~> 1.17'
gem 'terminal-table', '~> 3.0.0', require: false
gem 'slack-notifier', '~> 2.4', require: false
gem 'fog-google', '~> 1.17', require: false
+gem "warning", "~> 1.3"
gem 'confiner', '~> 0.3'
gem 'chemlab', '~> 0.9'
gem 'chemlab-library-www-gitlab-com', '~> 0.1'
+# dependencies for jenkins client
+gem 'nokogiri', '~> 1.12'
+
gem 'deprecation_toolkit', '~> 1.5.1', require: false
group :development do
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 71484c30c9a..ff382788c5a 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -148,8 +148,8 @@ GEM
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.9.0)
google-apis-core (>= 0.4, < 2.a)
- google-cloud-env (1.5.0)
- faraday (>= 0.17.3, < 2.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
googleauth (1.1.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
@@ -198,12 +198,12 @@ GEM
multi_xml (0.6.0)
multipart-post (2.1.1)
netrc (0.11.0)
- nokogiri (1.13.6)
+ nokogiri (1.13.8)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
- octokit (4.21.0)
- faraday (>= 0.9)
- sawyer (~> 0.8.0, >= 0.5.3)
+ octokit (4.25.1)
+ faraday (>= 1, < 3)
+ sawyer (~> 0.9)
oj (3.13.11)
os (1.1.4)
parallel (1.19.2)
@@ -270,9 +270,9 @@ GEM
rake (>= 0.8.1)
ruby2_keywords (0.0.4)
rubyzip (2.3.2)
- sawyer (0.8.2)
+ sawyer (0.9.2)
addressable (>= 2.3.5)
- faraday (> 0.8, < 2.0)
+ faraday (>= 0.17.3, < 3)
selenium-webdriver (4.0.3)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
@@ -307,6 +307,7 @@ GEM
procto (~> 0.0.2)
uuid (2.3.9)
macaddr (~> 1.0)
+ warning (1.3.0)
watir (6.19.1)
regexp_parser (>= 1.2, < 3)
selenium-webdriver (>= 3.142.7)
@@ -337,6 +338,7 @@ DEPENDENCIES
gitlab-qa (~> 7)
influxdb-client (~> 1.17)
knapsack (~> 4.0)
+ nokogiri (~> 1.12)
octokit (~> 4.21)
parallel (~> 1.19)
parallel_tests (~> 2.29)
@@ -354,6 +356,7 @@ DEPENDENCIES
slack-notifier (~> 2.4)
terminal-table (~> 3.0.0)
timecop (~> 0.9.1)
+ warning (~> 1.3)
webdrivers (~> 5.0)
zeitwerk (~> 2.4)
diff --git a/qa/Rakefile b/qa/Rakefile
index e4d38d8294f..d3e39d8ed1e 100644
--- a/qa/Rakefile
+++ b/qa/Rakefile
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-# rubocop:disable Rails/RakeEnvironment
require_relative "qa"
@@ -86,4 +85,8 @@ namespace :test_resources do
QA::Tools::TestResourcesHandler.new.download(args[:ci_project_name])
end
end
-# rubocop:enable Rails/RakeEnvironment
+
+desc "Deletes user's projects"
+task :delete_user_projects, [:delete_before, :dry_run] do |t, args|
+ QA::Tools::DeleteUserProjects.new(args).run
+end
diff --git a/qa/qa.rb b/qa/qa.rb
index 7d2f363143b..dd6462cfe27 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -17,8 +17,6 @@ require 'active_support/core_ext/hash'
require 'active_support/core_ext/object/blank'
require 'rainbow/refinement'
-require_relative 'qa/support/fips'
-
module QA
root = "#{__dir__}/qa"
@@ -67,7 +65,8 @@ module QA
"registry_tls" => "RegistryTLS",
"jetbrains" => "JetBrains",
"vscode" => "VSCode",
- "registry_with_cdn" => "RegistryWithCDN"
+ "registry_with_cdn" => "RegistryWithCDN",
+ "fips" => "FIPS"
)
# Configure knapsack at the very begining of the setup
@@ -77,3 +76,10 @@ module QA
loader.setup
end
+
+# Custom warning processing
+Warning.process do |warning|
+ QA::Runtime::Logger.warn(warning.strip)
+end
+
+Warning.ignore(/already initialized constant Chemlab::Vendor|previous definition of Vendor was here/)
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index bf08f887c7d..981b60d1920 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -6,6 +6,16 @@ module QA
extend self
def perform_before_hooks
+ if QA::Runtime::Env.admin_personal_access_token.present?
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username,
+ QA::Runtime::Env.admin_personal_access_token)
+ end
+
+ if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present?
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username,
+ QA::Runtime::Env.personal_access_token)
+ end
+
# The login page could take some time to load the first time it is visited.
# We visit the login page and wait for it to properly load only once before the tests.
QA::Runtime::Logger.info("Performing sanity check for environment!")
diff --git a/qa/qa/flow/purchase.rb b/qa/qa/flow/purchase.rb
index 5558e177685..e0efa8a8178 100644
--- a/qa/qa/flow/purchase.rb
+++ b/qa/qa/flow/purchase.rb
@@ -108,3 +108,5 @@ module QA
end
end
end
+
+QA::Flow::Purchase.prepend_mod_with('Flow::Purchase', namespace: QA)
diff --git a/qa/qa/page/admin/overview/users/components/impersonation_tokens.rb b/qa/qa/page/admin/overview/users/components/impersonation_tokens.rb
new file mode 100644
index 00000000000..0d0c92ce29d
--- /dev/null
+++ b/qa/qa/page/admin/overview/users/components/impersonation_tokens.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Overview
+ module Users
+ module Components
+ class ImpersonationTokens < Page::Base
+ include Page::Component::AccessTokens
+ include Page::Component::ConfirmModal
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/admin/overview/users/show.rb b/qa/qa/page/admin/overview/users/show.rb
index be73f3d80bf..2fde3ac2c6d 100644
--- a/qa/qa/page/admin/overview/users/show.rb
+++ b/qa/qa/page/admin/overview/users/show.rb
@@ -8,6 +8,7 @@ module QA
class Show < QA::Page::Base
view 'app/views/admin/users/_head.html.haml' do
element :impersonate_user_link
+ element :impersonation_tokens_tab
end
view 'app/views/admin/users/show.html.haml' do
@@ -32,6 +33,11 @@ module QA
click_element(:user_actions_dropdown_toggle, username: user.username)
end
+ def go_to_impersonation_tokens(&block)
+ navigate_to_tab(:impersonation_tokens_tab)
+ Users::Components::ImpersonationTokens.perform(&block)
+ end
+
def click_impersonate_user
click_element(:impersonate_user_link)
end
@@ -50,6 +56,20 @@ module QA
click_element :approve_user_button
click_element :approve_user_confirm_button
end
+
+ private
+
+ def navigate_to_tab(element_name)
+ wait_until(reload: false) do
+ click_element element_name unless on_impersontation_tokens_tab?
+
+ on_impersontation_tokens_tab?(wait: 10)
+ end
+ end
+
+ def on_impersontation_tokens_tab?(wait: 1)
+ has_css?(".active", text: 'Impersonation Tokens', wait: wait)
+ end
end
end
end
diff --git a/qa/qa/page/alert/auto_devops_alert.rb b/qa/qa/page/alert/auto_devops_alert.rb
index 8f66c805b77..26801c4996c 100644
--- a/qa/qa/page/alert/auto_devops_alert.rb
+++ b/qa/qa/page/alert/auto_devops_alert.rb
@@ -5,7 +5,7 @@ module QA
module Alert
class AutoDevopsAlert < Page::Base
view 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' do
- element :auto_devops_banner
+ element :auto_devops_banner_content
end
end
end
diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb
index f143e5b9e1f..36c0f8c2f00 100644
--- a/qa/qa/page/component/access_tokens.rb
+++ b/qa/qa/page/component/access_tokens.rb
@@ -23,6 +23,10 @@ module QA
element :create_token_button
end
+ base.view 'app/views/shared/access_tokens/_table.html.haml' do
+ element :revoke_button
+ end
+
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
@@ -54,7 +58,11 @@ module QA
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"
+ begin
+ Date.strptime(date, '%Y-%m-%d')
+ rescue ArgumentError
+ raise "Expiry date must be in YYYY-MM-DD format"
+ end
fill_element(:expiry_date_field, date)
end
diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb
index 4c053f1d6a9..2ba198621fc 100644
--- a/qa/qa/page/component/ci_badge_link.rb
+++ b/qa/qa/page/component/ci_badge_link.rb
@@ -40,8 +40,6 @@ module QA
find_element(:status_badge).text
end
- private
-
def completed?(timeout: 60)
wait_until(reload: false, sleep_interval: 3.0, max_duration: timeout) do
COMPLETED_STATUSES.include?(status_badge)
diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb
index 76200490f66..4d1cf30c392 100644
--- a/qa/qa/page/component/confirm_modal.rb
+++ b/qa/qa/page/component/confirm_modal.rb
@@ -12,21 +12,26 @@ module QA
base.view 'app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue' do
element :confirm_ok_button
end
+
+ base.view 'app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue' do
+ element :confirm_danger_modal_button
+ element :confirm_danger_field
+ end
end
def fill_confirmation_text(text)
- fill_element(:confirm_input, text)
+ fill_element(:confirm_danger_field, text)
end
def wait_for_confirm_button_enabled
wait_until(reload: false) do
- !find_element(:confirm_button).disabled?
+ !find_element(:confirm_danger_modal_button).disabled?
end
end
def confirm_transfer
wait_for_confirm_button_enabled
- click_element(:confirm_button)
+ click_element(:confirm_danger_modal_button)
end
def click_confirmation_ok_button
diff --git a/qa/qa/page/component/groups_filter.rb b/qa/qa/page/component/groups_filter.rb
index f82bb81a3fc..ff61c91f0f6 100644
--- a/qa/qa/page/component/groups_filter.rb
+++ b/qa/qa/page/component/groups_filter.rb
@@ -10,7 +10,7 @@ module QA
super
base.view 'app/views/shared/groups/_search_form.html.haml' do
- element :groups_filter
+ element :groups_filter_field
end
base.view 'app/assets/javascripts/groups/components/groups.vue' do
@@ -22,7 +22,7 @@ module QA
def has_filtered_group?(name)
# Filter and submit to reload the page and only retrieve the filtered results
- find_element(:groups_filter).set(name).send_keys(:return)
+ find_element(:groups_filter_field).set(name).send_keys(:return)
# Since we submitted after filtering, the presence of
# groups_list_tree_container means we have the complete filtered list
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
index 4131731111f..68da89dc81d 100644
--- a/qa/qa/page/component/issuable/sidebar.rb
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -35,7 +35,7 @@ module QA
end
base.view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :assignee_block
+ element :assignee_block_container
element :milestone_block
end
@@ -127,7 +127,7 @@ module QA
private
def wait_assignees_block_finish_loading
- within_element(:assignee_block) do
+ within_element(:assignee_block_container) do
wait_until(reload: false, max_duration: 10, sleep_interval: 1) do
finished_loading_block?
yield
diff --git a/qa/qa/page/component/namespace_select.rb b/qa/qa/page/component/namespace_select.rb
index 924e1af876c..4dbcb39ced6 100644
--- a/qa/qa/page/component/namespace_select.rb
+++ b/qa/qa/page/component/namespace_select.rb
@@ -13,6 +13,7 @@ module QA
element :namespaces_list
element :namespaces_list_groups
element :namespaces_list_item
+ element :namespaces_list_search
end
end
@@ -20,6 +21,10 @@ module QA
click_element :namespaces_list
within_element(:namespaces_list) do
+ find_element(:namespaces_list_search).fill_in(with: item)
+
+ wait_for_requests
+
find_element(:namespaces_list_item, text: item).click
end
end
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index 74b6c6b2d5e..22b9a4c8b0d 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -48,7 +48,9 @@ module QA
end
def use_new_editor
- click_element(:editing_mode_button, mode: 'Edit rich text')
+ within_element(:editing_mode_button) do
+ find('label', text: 'Rich text').click
+ end
wait_until(reload: false) do
has_element?(:content_editor_container)
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index 52853376f17..644d19d6bcb 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -6,13 +6,8 @@ module QA
class Groups < Page::Base
include Page::Component::GroupsFilter
- view 'app/views/shared/groups/_search_form.html.haml' do
- element :groups_filter, 'search_field_tag :filter' # rubocop:disable QA/ElementWithPattern
- element :groups_filter_placeholder, 'Search by name' # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/views/dashboard/_groups_head.html.haml' do
- element :new_group_button, 'link_to _("New group")' # rubocop:disable QA/ElementWithPattern
+ element :new_group_button
end
def has_group?(name)
@@ -26,7 +21,7 @@ module QA
end
def click_new_group
- click_on 'New group'
+ click_element(:new_group_button)
end
end
end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index a0b42598962..10529ed69e1 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -5,7 +5,7 @@ module QA
module Dashboard
class Projects < Page::Base
view 'app/views/shared/projects/_search_form.html.haml' do
- element :project_filter_form, required: true
+ element :project_filter_form_container, required: true
end
view 'app/views/shared/projects/_project.html.haml' do
@@ -24,7 +24,7 @@ module QA
end
def filter_by_name(name)
- within_element(:project_filter_form) do
+ within_element(:project_filter_form_container) do
fill_in :name, with: name
end
end
@@ -44,7 +44,7 @@ module QA
end
def clear_project_filter
- fill_element(:project_filter_form, "")
+ fill_element(:project_filter_form_container, "")
end
end
end
diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb
index 3a4a1837b1c..d2b8c8260fd 100644
--- a/qa/qa/page/file/edit.rb
+++ b/qa/qa/page/file/edit.rb
@@ -7,6 +7,30 @@ module QA
include Shared::CommitMessage
include Shared::CommitButton
include Shared::Editor
+
+ view 'app/assets/javascripts/editor/components/source_editor_toolbar_button.vue' do
+ element :editor_toolbar_button
+ end
+
+ view 'app/views/projects/blob/_editor.html.haml' do
+ element :source_editor_preview_container
+ end
+
+ def has_markdown_preview?(component, content)
+ within_element(:source_editor_preview_container) do
+ has_css?(component, exact_text: content)
+ end
+ end
+
+ def wait_for_markdown_preview(component, content)
+ return if has_markdown_preview?(component, content)
+
+ raise ElementNotFound, %("Couldn't find #{component} element with content '#{content}')
+ end
+
+ def click_editor_toolbar
+ click_element(:editor_toolbar_button)
+ end
end
end
end
diff --git a/qa/qa/page/file/shared/editor.rb b/qa/qa/page/file/shared/editor.rb
index ce4465d2a5c..dab02c1e34f 100644
--- a/qa/qa/page/file/shared/editor.rb
+++ b/qa/qa/page/file/shared/editor.rb
@@ -20,7 +20,11 @@ module QA
end
def remove_content
- text_area.send_keys([:command, 'a'], :backspace)
+ if page.driver.browser.capabilities.platform.include? "mac"
+ text_area.send_keys([:command, 'a'], :backspace)
+ else
+ text_area.send_keys([:control, 'a'], :backspace)
+ end
end
private
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index 09a9af7aaf7..1f17b470ada 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -12,7 +12,7 @@ module QA
end
view 'app/views/groups/_new_group_fields.html.haml' do
- element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern
+ element :create_group_button
end
view 'app/views/groups/_import_group_from_another_instance_panel.html.haml' do
@@ -34,6 +34,10 @@ module QA
click_button 'Create group'
end
+ def create_subgroup
+ click_button 'Create subgroup'
+ end
+
def set_gitlab_url(url)
fill_element(:import_gitlab_url, url)
end
diff --git a/qa/qa/page/issuable/new.rb b/qa/qa/page/issuable/new.rb
index 0c95f722080..f3e6a84ef54 100644
--- a/qa/qa/page/issuable/new.rb
+++ b/qa/qa/page/issuable/new.rb
@@ -5,11 +5,7 @@ module QA
module Issuable
class New < Page::Base
view 'app/views/shared/issuable/form/_title.html.haml' do
- element :issuable_form_title
- end
-
- view 'app/views/shared/issuable/form/_metadata.html.haml' do
- element :issuable_milestone_dropdown
+ element :issuable_form_title_field
end
view 'app/views/shared/form_elements/_description.html.haml' do
@@ -17,11 +13,12 @@ module QA
end
view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do
- element :issuable_dropdown_menu_milestone
+ element :issuable_milestone_dropdown
+ element :issuable_milestone_dropdown_content
end
view 'app/views/shared/issuable/_label_dropdown.html.haml' do
- element :issuable_label
+ element :issuable_label_dropdown
end
view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do
@@ -33,7 +30,7 @@ module QA
end
def fill_title(title)
- fill_element :issuable_form_title, title
+ fill_element :issuable_form_title_field, title
end
def fill_description(description)
@@ -42,7 +39,7 @@ module QA
def choose_milestone(milestone)
click_element :issuable_milestone_dropdown
- within_element(:issuable_dropdown_menu_milestone) do
+ within_element(:issuable_milestone_dropdown_content) do
click_on milestone.title
end
end
@@ -55,11 +52,11 @@ module QA
end
def select_label(label)
- click_element :issuable_label
+ click_element :issuable_label_dropdown
click_link label.title
- click_element :issuable_label # So that the dropdown goes away(click away action)
+ click_element :issuable_label_dropdown # So that the dropdown goes away(click away action)
end
def assign_to_me
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index e73d40b37ac..e19bc0838c9 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -7,26 +7,26 @@ module QA
include Component::LazyLoader
view 'app/views/shared/labels/_nav.html.haml' do
- element :label_create_new
+ element :create_new_label_button
end
view 'app/views/shared/empty_states/_labels.html.haml' do
- element :label_svg
+ element :label_svg_content
end
view 'app/views/shared/empty_states/_priority_labels.html.haml' do
- element :label_svg
+ element :label_svg_content
end
def click_new_label_button
# The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit
# This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?)
# before clicking the button.
- within_element(:label_svg) do
+ within_element(:label_svg_content) do
has_element?(:js_lazy_loaded)
end
- click_element :label_create_new
+ click_element :create_new_label_button
end
end
end
diff --git a/qa/qa/page/label/new.rb b/qa/qa/page/label/new.rb
index a40179489c1..12427443280 100644
--- a/qa/qa/page/label/new.rb
+++ b/qa/qa/page/label/new.rb
@@ -5,9 +5,9 @@ module QA
module Label
class New < Page::Base
view 'app/views/shared/labels/_form.html.haml' do
- element :label_title
- element :label_description
- element :label_color
+ element :label_title_field
+ element :label_description_field
+ element :label_color_field
element :label_create_button
end
@@ -16,15 +16,15 @@ module QA
end
def fill_title(title)
- fill_element :label_title, title
+ fill_element :label_title_field, title
end
def fill_description(description)
- fill_element :label_description, description
+ fill_element :label_description_field, description
end
def fill_color(color)
- fill_element :label_color, color
+ fill_element :label_color_field, color
end
end
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index f3ee627c41e..d7ca8223862 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -183,8 +183,7 @@ module QA
switch_to_sign_in_tab if has_sign_in_tab?
switch_to_standard_tab if has_standard_tab?
- fill_element :login_field, user.username
- fill_element :password_field, user.password
+ fill_in_credential(user)
if Runtime::Env.running_on_dot_com?
click_accept_all_cookies if has_accept_all_cookies_button?
@@ -211,6 +210,11 @@ module QA
Page::Main::Menu.validate_elements_present! unless skip_page_validation
end
+ def fill_in_credential(user)
+ fill_element :login_field, user.username
+ fill_element :password_field, user.password
+ end
+
# Handle request for password change
# Happens on clean GDK installations when seeded root admin password is expired
#
@@ -236,3 +240,5 @@ module QA
end
end
end
+
+QA::Page::Main::Login.prepend_mod_with('Page::Main::Login', namespace: QA)
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 27c12a4e21f..9fc0cf0ccf8 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -11,6 +11,7 @@ module QA
element :review_preview_dropdown
end
+ # Remove once :mr_review_submit_comment ff is enabled by default
view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do
element :submit_review_button
end
@@ -19,8 +20,9 @@ module QA
element :review_bar_content
end
- view 'app/assets/javascripts/batch_comments/components/draft_note.vue' do
- element :draft_note_content
+ view 'app/assets/javascripts/batch_comments/components/submit_dropdown.vue' do
+ element :submit_review_dropdown
+ element :submit_review_button
end
view 'app/assets/javascripts/diffs/components/compare_dropdown_layout.vue' do
@@ -71,7 +73,6 @@ module QA
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
element :cherry_pick_button
- element :merged_status_content
element :revert_button
end
@@ -134,6 +135,11 @@ module QA
element :cancel_auto_merge_button
end
+ view 'app/views/shared/_broadcast_message.html.haml' do
+ element :broadcast_notification_container
+ element :close_button
+ end
+
def start_review
click_element(:start_review_button)
@@ -151,16 +157,34 @@ module QA
end
def submit_pending_reviews
- has_element?(:submit_review_button)
+ # On test environments we have a broadcast message that can cover the buttons
+
+ if has_element?(:broadcast_notification_container, wait: 5)
+ within_element(:broadcast_notification_container) do
+ click_element(:close_button)
+ end
+ end
+
within_element(:review_bar_content) do
click_element(:review_preview_dropdown)
end
- within_element(:draft_note_content) do
+
+ # Remove if statement once :mr_review_submit_comment ff is enabled by default
+
+ if has_element?(:submit_review_dropdown, wait: 5)
+ click_element(:submit_review_dropdown)
click_element(:submit_review_button)
+ else
+ within_element(:review_bar_content) do
+ click_element(:submit_review_button)
+ end
end
- # After clicking the button, wait for it to disappear
+
+ # After clicking the button, wait for the review bar to disappear
# before moving on to the next part of the test
- has_no_element?(:submit_review_button)
+ wait_until(reload: false) do
+ has_no_element?(:review_bar_content)
+ end
end
def add_comment_to_diff(text)
diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb
index 8da484003f4..db71062cec6 100644
--- a/qa/qa/page/profile/ssh_keys.rb
+++ b/qa/qa/page/profile/ssh_keys.rb
@@ -31,7 +31,11 @@ module QA
def fill_expiry_date(date)
date = date.strftime('%m/%d/%Y') if date.is_a?(Date)
- Date.strptime(date, '%m/%d/%Y') rescue ArgumentError raise "Expiry date must be in mm/dd/yyyy format"
+ begin
+ Date.strptime(date, '%m/%d/%Y')
+ rescue ArgumentError
+ raise "Expiry date must be in mm/dd/yyyy format"
+ end
fill_element(:key_expiry_date_field, date)
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 7864e664429..bb4fb84f2d2 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -81,9 +81,9 @@ module QA
# Disable experiment for SAST at project creation https://gitlab.com/gitlab-org/gitlab/-/issues/333196
def disable_initialize_with_sast
- return unless has_element?(:initialize_with_sast_checkbox)
+ return unless has_element?(:initialize_with_sast_checkbox, visible: false)
- uncheck_element(:initialize_with_sast_checkbox)
+ uncheck_element(:initialize_with_sast_checkbox, true)
end
def click_github_link
@@ -95,7 +95,7 @@ module QA
end
def disable_initialize_with_readme
- uncheck_element(:initialize_with_readme_checkbox)
+ uncheck_element(:initialize_with_readme_checkbox, true)
end
end
end
diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb
index 78f16a8a65c..70c0c5abb52 100644
--- a/qa/qa/page/project/pipeline_editor/show.rb
+++ b/qa/qa/page/project/pipeline_editor/show.rb
@@ -54,7 +54,12 @@ module QA
element :file_tree_popover
end
+ view 'app/assets/javascripts/pipeline_editor/components/validate/ci_validate.vue' do
+ element :simulate_pipeline_button
+ end
+
def initialize
+ wait_for_requests(skip_finished_loading_check: true)
dismiss_file_tree_popover if has_element?(:file_tree_popover)
super
@@ -89,7 +94,7 @@ module QA
end
def submit_changes
- Support::Waiter.wait_until { !find_element(:commit_changes_button).disabled? }
+ wait_until(reload: false) { !find_element(:commit_changes_button).disabled? }
click_element(:commit_changes_button)
wait_for_requests
@@ -115,14 +120,14 @@ module QA
go_to_tab('Visualize')
end
- def go_to_lint_tab
- go_to_tab('Lint')
- end
-
def go_to_view_merged_yaml_tab
go_to_tab('View merged YAML')
end
+ def go_to_validate_tab
+ go_to_tab('Validate')
+ end
+
def has_source_editor?
has_element?(:source_editor_container)
end
@@ -141,6 +146,12 @@ module QA
end
end
+ def tab_alert_title
+ within_element(:file_editor_container) do
+ find('.gl-alert-title').text
+ end
+ end
+
def has_new_mr_checkbox?
has_element?(:new_mr_checkbox, visible: true)
end
@@ -153,6 +164,10 @@ module QA
check_element(:new_mr_checkbox, true)
end
+ def simulate_pipeline
+ click_element(:simulate_pipeline_button)
+ end
+
private
def go_to_tab(name)
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index fd9cc8a13e7..9d8ed132ffd 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -8,6 +8,10 @@ module QA
include QA::Page::Component::ConfirmModal
include Component::NamespaceSelect
+ view 'app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue' do
+ element :confirm_danger_button
+ end
+
view 'app/views/projects/edit.html.haml' do
element :project_path_field
element :change_path_button
@@ -47,7 +51,7 @@ module QA
# https://gitlab.com/gitlab-org/gitlab/-/issues/218965
select_namespace(namespace.gsub(%r{([^\s])/([^\s])}, '\1 / \2'))
- click_element(:transfer_button)
+ click_element(:confirm_danger_button)
fill_confirmation_text(project_name)
confirm_transfer
end
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
index 0d5515aacdf..58b3badbb22 100644
--- a/qa/qa/page/project/settings/integrations.rb
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -6,6 +6,7 @@ module QA
module Settings
class Integrations < QA::Page::Base
view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
+ element :jenkins_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
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
element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
@@ -22,10 +23,12 @@ module QA
def click_jira_link
click_element :jira_link
end
+
+ def click_jenkins_ci_link
+ click_element :jenkins_link
+ end
end
end
end
end
end
-
-QA::Page::Project::Settings::Integrations.prepend_mod_with('Page::Project::Settings::Integrations', namespace: QA)
diff --git a/qa/qa/page/project/settings/pages.rb b/qa/qa/page/project/settings/pages.rb
new file mode 100644
index 00000000000..1417f7ec1b5
--- /dev/null
+++ b/qa/qa/page/project/settings/pages.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class Pages < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/projects/pages/_access.html.haml' do
+ element :access_page_container
+ end
+
+ def go_to_access_page
+ within_element(:access_page_container) do
+ find('a').click
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Settings::Pages.prepend_mod_with("Page::Project::Settings::Pages", namespace: QA)
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 308cf6366a7..35fc87f717c 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -17,10 +17,6 @@ module QA
element :allowed_to_merge_dropdown
end
- view 'app/views/shared/projects/protected_branches/_update_protected_branch.html.haml' do
- element :allowed_to_merge
- end
-
view 'app/views/projects/protected_branches/shared/_branches_list.html.haml' do
element :protected_branches_list
end
diff --git a/qa/qa/page/project/settings/services/jenkins.rb b/qa/qa/page/project/settings/services/jenkins.rb
index 8e092371491..39403995ce8 100644
--- a/qa/qa/page/project/settings/services/jenkins.rb
+++ b/qa/qa/page/project/settings/services/jenkins.rb
@@ -17,11 +17,11 @@ module QA
element :save_changes_button
end
- def setup_service_with(jenkins_url:, project_name:)
+ def setup_service_with(jenkins_url:, project_name:, username:, password:)
set_jenkins_url(jenkins_url)
set_project_name(project_name)
- set_username('admin')
- set_password('password')
+ set_username(username)
+ set_password(password)
click_save_changes_button
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 022e08215be..e048afee8b3 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -67,8 +67,8 @@ module QA
end
view 'app/views/shared/_ref_switcher.html.haml' do
- element :branches_select
element :branches_dropdown
+ element :branches_dropdown_content
end
view 'app/views/projects/blob/viewers/_loading.html.haml' do
@@ -176,9 +176,9 @@ module QA
end
def switch_to_branch(branch_name)
- find_element(:branches_select).click
+ find_element(:branches_dropdown).click
- within_element(:branches_dropdown) do
+ within_element(:branches_dropdown_content) do
click_on branch_name
end
end
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index f35d27e658d..53a5eaf60c5 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -69,6 +69,14 @@ module QA
end
end
+ def go_to_pages_settings
+ hover_settings do
+ within_submenu do
+ click_element(:sidebar_menu_item_link, menu_item: 'Pages')
+ end
+ end
+ end
+
private
def hover_settings
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index bc6c839bfe2..e572569e496 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -24,11 +24,11 @@ module QA
end
view 'app/assets/javascripts/ide/components/ide_tree_list.vue' do
- element :file_list
+ element :file_list_container
end
view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do
- element :file_templates_bar
+ element :file_templates_container
element :file_template_dropdown
end
@@ -110,7 +110,7 @@ module QA
end
def has_file?(file_name)
- within_element(:file_list) do
+ within_element(:file_list_container) do
has_element?(:file_name_content, file_name: file_name)
end
end
@@ -173,7 +173,7 @@ module QA
has_no_element?(:new_file_modal)
wait_until(reload: false) do
- within_element(:file_templates_bar) do
+ within_element(:file_templates_container) do
click_element :file_template_dropdown
fill_element :dropdown_filter_input, template
@@ -291,12 +291,12 @@ module QA
click_element(:fork_project_button)
# wait for the fork to be created
wait_until(reload: true) do
- has_element?(:file_list)
+ has_element?(:file_list_container)
end
end
def upload_file(file_path)
- within_element(:file_list) do
+ within_element(:file_list_container) do
find_element(:file_upload_field, visible: false).send_keys(file_path)
end
end
diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb
index fa17b8fe302..6a6396b8ed4 100644
--- a/qa/qa/page/view.rb
+++ b/qa/qa/page/view.rb
@@ -37,7 +37,7 @@ module QA
def self.evaluate(&block)
Page::View::DSL.new.tap do |evaluator|
- evaluator.instance_exec(&block) if block_given?
+ evaluator.instance_exec(&block) if block
end
end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 667dbc03fc3..d1cfdfbc16c 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -74,6 +74,13 @@ module QA
response.code == HTTP_STATUS_OK
end
+ # Parameters included in the query URL
+ #
+ # @return [Hash]
+ def query_parameters
+ @query_parameters ||= {}
+ end
+
private
def resource_web_url(resource)
@@ -87,7 +94,8 @@ module QA
end
def api_get_from(get_path)
- request = Runtime::API::Request.new(api_client, get_path)
+ path = "#{get_path}#{query_parameters_to_string}"
+ request = Runtime::API::Request.new(api_client, path)
response = get(request.url)
if response.code == HTTP_STATUS_SERVER_ERROR
@@ -101,6 +109,15 @@ module QA
response
end
+ # Query parameters formatted as `?key1=value1&key2=value2...`
+ #
+ # @return [String]
+ def query_parameters_to_string
+ query_parameters.each_with_object([]) do |(k, v), arr|
+ arr << "#{k}=#{v}"
+ end.join('&').prepend('?').chomp('?') # prepend `?` unless the string is blank
+ end
+
def api_post
process_api_response(api_post_to(api_post_path, api_post_body))
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index c3da9d47de5..60f6cbdfc51 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -38,10 +38,8 @@ module QA
group_show.go_to_new_subgroup
Page::Group::New.perform do |group_new|
- group_new.click_create_group
group_new.set_path(path)
- group_new.set_visibility('Public')
- group_new.create
+ group_new.create_subgroup
end
# Ensure that the group was actually created
@@ -63,6 +61,13 @@ module QA
"/groups/#{CGI.escape(determine_full_path)}"
end
+ # Parameters included in the query URL
+ #
+ # @return [Hash]
+ def query_parameters
+ super.merge({ with_projects: false })
+ end
+
def api_post_body
{
parent_id: sandbox.id,
diff --git a/qa/qa/resource/group_ci_variable.rb b/qa/qa/resource/group_ci_variable.rb
new file mode 100644
index 00000000000..f78d11b6c11
--- /dev/null
+++ b/qa/qa/resource/group_ci_variable.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class GroupCiVariable < Base
+ attr_accessor :key, :value, :masked, :protected
+
+ attribute :group do
+ QA::Resource::Group.fabricate_via_api!
+ end
+
+ def initialize
+ @masked = false
+ @protected = false
+ end
+
+ def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
+ super
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "/groups/#{group.id}/variables/#{key}"
+ end
+
+ def api_post_path
+ "/groups/#{group.id}/variables"
+ end
+
+ def api_post_body
+ {
+ key: key,
+ value: value,
+ masked: masked,
+ protected: protected
+ }
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/impersonation_token.rb b/qa/qa/resource/impersonation_token.rb
new file mode 100644
index 00000000000..3bd356b5e9b
--- /dev/null
+++ b/qa/qa/resource/impersonation_token.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class ImpersonationToken < Base
+ attr_writer :name
+
+ attribute :id
+ attribute :user
+ attribute :token
+ attribute :expires_at
+
+ def api_get_path
+ "/users/#{user.id}/impersonation_tokens/#{id}"
+ rescue NoValueError
+ token = parse_body(api_get_from("/users/#{user.id}/impersonation_tokens")).find { |t| t[:name] == name }
+
+ raise ResourceNotFoundError unless token
+
+ @id = token[:id]
+ retry
+ end
+
+ def api_post_path
+ api_get_path
+ end
+
+ def name
+ @name ||= "api-impersonation-access-token-#{Faker::Alphanumeric.alphanumeric(number: 8)}"
+ end
+
+ def api_post_body
+ {
+ name: name,
+ scopes: ["api"],
+ expires_at: expires_at.to_s
+ }
+ end
+
+ def api_delete_path
+ "/users/#{user.id}/impersonation_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_browser_ui!
+ Flow::Login.sign_in_unless_signed_in(user: Runtime::User.admin)
+
+ Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Admin::Menu.perform(&:go_to_users_overview)
+ Page::Admin::Overview::Users::Index.perform do |index|
+ index.search_user(user.username)
+ index.click_user(user.name)
+ end
+
+ Page::Admin::Overview::Users::Show.perform do |show|
+ show.go_to_impersonation_tokens do |impersonation_tokens|
+ impersonation_tokens.revoke_first_token_with_name(name)
+ end
+ end
+ yield if block_given?
+ end
+
+ # Expire in 2 days just in case the token is created just before midnight
+ def expires_at
+ @expires_at || Time.now.utc.to_date + 2
+ end
+
+ def fabricate!
+ Flow::Login.sign_in_unless_signed_in(user: Runtime::User.admin)
+
+ Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Admin::Menu.perform(&:go_to_users_overview)
+ Page::Admin::Overview::Users::Index.perform do |index|
+ index.search_user(user.username)
+ index.click_user(user.name)
+ end
+
+ Page::Admin::Overview::Users::Show.perform do |show|
+ show.go_to_impersonation_tokens do |impersonation_tokens|
+ impersonation_tokens.fill_token_name(name)
+ impersonation_tokens.check_api
+ impersonation_tokens.fill_expiry_date(expires_at)
+ impersonation_tokens.click_create_token_button
+ self.token = impersonation_tokens.created_access_token
+ end
+ end
+
+ reload!
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/integrations/project.rb b/qa/qa/resource/integrations/project.rb
new file mode 100644
index 00000000000..11e59408e22
--- /dev/null
+++ b/qa/qa/resource/integrations/project.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Integrations
+ module Project
+ def find_integration(slug)
+ fetch_integrations.find do |integration|
+ integration[:slug] == slug
+ end
+ end
+
+ def fetch_integrations
+ parse_body api_get_from(api_get_integrations)
+ end
+
+ private
+
+ def api_get_integrations
+ "#{api_get_path}/integrations"
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb
index a12c210ea0e..ad0f183c603 100644
--- a/qa/qa/resource/personal_access_token.rb
+++ b/qa/qa/resource/personal_access_token.rb
@@ -17,20 +17,17 @@ module QA
# If Runtime::Env.admin_personal_access_token is provided, fabricate via the API,
# else, fabricate via the browser.
def fabricate_via_api!
- QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username).tap do |cached_token|
- @token = cached_token if cached_token
- end
- return if @token
+ return if find_and_set_value
resource = if Runtime::Env.admin_personal_access_token && !@user.nil?
self.api_client = Runtime::API::Client.as_admin
-
super
else
fabricate!
end
- QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, token) if @user
+ self.token = api_response[:token] unless api_response.nil?
+ cache_token
resource
end
@@ -60,7 +57,17 @@ module QA
# this particular resource does not expose a web_url property
end
+ def find_and_set_value
+ @token ||= QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username)
+ end
+
+ def cache_token
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, self.token) if @user && self.token
+ end
+
def fabricate!
+ return if find_and_set_value
+
Flow::Login.sign_in_unless_signed_in(user: user)
Page::Main::Menu.perform(&:click_edit_profile_link)
@@ -74,6 +81,10 @@ module QA
token_page.click_create_token_button
self.token = Page::Profile::PersonalAccessTokens.perform(&:created_access_token)
+
+ cache_token
+
+ self.token
end
end
end
diff --git a/qa/qa/resource/personal_access_token_cache.rb b/qa/qa/resource/personal_access_token_cache.rb
index 3e9dc3fd7df..0874618201a 100644
--- a/qa/qa/resource/personal_access_token_cache.rb
+++ b/qa/qa/resource/personal_access_token_cache.rb
@@ -6,7 +6,17 @@ module QA
@personal_access_tokens = {}
def self.get_token_for_username(username)
- @personal_access_tokens[username]
+ token = @personal_access_tokens[username]
+
+ log_message = if token
+ %Q[Retrieved cached token for username: #{username}, last six chars of token:#{token[-6..]}]
+ else
+ %Q[No cached token found for username: #{username}]
+ end
+
+ QA::Runtime::Logger.info(log_message)
+
+ token
end
def self.set_token_for_username(username, token)
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 825041cbead..13c6f285259 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -4,6 +4,7 @@ module QA
module Resource
class Project < Base
include Events::Project
+ include Integrations::Project
include Members
include Visibility
@@ -20,13 +21,13 @@ module QA
:name,
:path,
:add_name_uuid,
- :description,
:runners_token,
:visibility,
:template_name,
:import,
:import_status,
- :import_error
+ :import_error,
+ :description
attribute :group do
Group.fabricate! do |group|
@@ -107,7 +108,7 @@ module QA
end
new_page.choose_name(@name)
- new_page.add_description(@description)
+ new_page.add_description(@description) if @description
new_page.set_visibility(@visibility)
new_page.disable_initialize_with_sast
new_page.disable_initialize_with_readme unless @initialize_with_readme
@@ -294,6 +295,21 @@ module QA
)
end
+ def change_path(new_path)
+ response = put(request_url(api_put_path), path: new_path)
+
+ unless response.code == HTTP_STATUS_OK
+ raise(
+ ResourceUpdateFailedError,
+ "Failed to update the project path to '#{new_path}'. Request returned (#{response.code}): `#{response}`."
+ )
+ end
+
+ # We need to manually set the path_with_namespace as reload! relies on it being correct and avoid 404s
+ result = parse_body(response)
+ @path_with_namespace = result[:path_with_namespace]
+ end
+
def default_branch
reload!.api_response[:default_branch] || Runtime::Env.default_branch
end
@@ -458,10 +474,12 @@ module QA
response = post(request_url(api_housekeeping_path), nil)
- unless response.code == HTTP_STATUS_CREATED
- raise ResourceQueryError,
- "Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
- end
+ return if response.code == HTTP_STATUS_CREATED
+
+ raise(
+ ResourceQueryError,
+ "Could not perform housekeeping. Request returned (#{response.code}): `#{response.body}`."
+ )
end
# Gets project statistics.
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 8e7527bccd4..12ecce1ce9a 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -57,6 +57,13 @@ module QA
"/groups/#{path}"
end
+ # Parameters included in the query URL
+ #
+ # @return [Hash]
+ def query_parameters
+ super.merge({ with_projects: false })
+ end
+
def api_post_body
{
path: path,
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index f7aca2571c9..1fd097d0acf 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -291,6 +291,14 @@ module QA
ENV['JIRA_HOSTNAME']
end
+ def jenkins_admin_username
+ ENV.fetch('QA_JENKINS_USER', 'administrator')
+ end
+
+ def jenkins_admin_password
+ ENV.fetch('QA_JENKINS_PASS', 'password')
+ end
+
# this is set by the integrations job
# which will allow bidirectional communication
# between the app and the specs container
@@ -484,6 +492,10 @@ module QA
ENV.fetch('MAX_CAPYBARA_WAIT_TIME', 10).to_i
end
+ def use_public_ip_api?
+ enabled?(ENV['QA_USE_PUBLIC_IP_API'], default: false)
+ end
+
private
def remote_grid_credentials
diff --git a/qa/qa/runtime/ip_address.rb b/qa/qa/runtime/ip_address.rb
index 657dc789cff..fcb6db750bb 100644
--- a/qa/qa/runtime/ip_address.rb
+++ b/qa/qa/runtime/ip_address.rb
@@ -13,7 +13,10 @@ module QA
def fetch_current_ip_address
# When running on CI against a live environment such as staging.gitlab.com,
# we use the public facing IP address
- ip_address = if Env.running_in_ci? && !URI.parse(Scenario.gitlab_address).host.include?('.test')
+ non_test_host = !URI.parse(Scenario.gitlab_address).host.include?('.test')
+ has_no_public_ip = Env.running_in_ci? || Env.use_public_ip_api?
+
+ ip_address = if has_no_public_ip && non_test_host
response = get(PUBLIC_IP_ADDRESS_API)
raise HostUnreachableError, "#{PUBLIC_IP_ADDRESS_API} is unreachable" unless response.code == Support::API::HTTP_STATUS_OK
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
index 0af42470a7c..e4eeca2000f 100644
--- a/qa/qa/runtime/user.rb
+++ b/qa/qa/runtime/user.rb
@@ -55,3 +55,5 @@ module QA
end
end
end
+
+QA::Runtime::User.extend_mod_with('Runtime::User', namespace: QA)
diff --git a/qa/qa/scenario/test/integration/metrics.rb b/qa/qa/scenario/test/integration/metrics.rb
new file mode 100644
index 00000000000..c1297038a0f
--- /dev/null
+++ b/qa/qa/scenario/test/integration/metrics.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class Metrics < Test::Instance::All
+ tags :metrics
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index 8b355c5faf6..688fede1b2e 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -19,7 +19,7 @@ module QA
validators.flat_map(&:errors).tap do |errors|
break if errors.none?
- warn <<~EOS
+ warn <<~WARN
GitLab QA sanity selectors validation test detected problems
with your merge request!
@@ -42,15 +42,14 @@ module QA
contribute, please open an issue in GitLab QA issue tracker.
Please see errors described below.
-
- EOS
+ WARN
warn errors
end
validators.each(&:validate!)
- puts 'Views / selectors validation passed!'
+ QA::Runtime::Logger.info('Views / selectors validation passed!')
end
end
end
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 1584b577af1..45ab4ceff99 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -36,13 +36,14 @@ module QA
end
def register!
- shell <<~CMD.tr("\n", ' ')
+ cmd = <<~CMD.tr("\n", ' ')
docker run -d --rm --network #{runner_network} --name #{@name}
#{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker}
--privileged
#{@image} #{add_gitlab_tls_cert if @address.include? 'https'}
&& docker exec --detach #{@name} sh -c "#{register_command}"
CMD
+ shell(cmd, mask_secrets: [@token])
wait_until_running_and_configured
diff --git a/qa/qa/service/docker_run/jenkins.rb b/qa/qa/service/docker_run/jenkins.rb
index 808fab80c63..88b69210382 100644
--- a/qa/qa/service/docker_run/jenkins.rb
+++ b/qa/qa/service/docker_run/jenkins.rb
@@ -4,11 +4,27 @@ module QA
module Service
module DockerRun
class Jenkins < Base
+ include Mixins::ThirdPartyDocker
+
+ attr_reader :port
+
def initialize
- @image = 'registry.gitlab.com/gitlab-org/gitlab-qa/jenkins-gitlab:version1'
+ @image = "#{third_party_repository}/jenkins:latest"
@name = 'jenkins-server'
@port = '8080'
- super()
+ super
+ end
+
+ def network
+ @network || 'test'
+ end
+
+ def username
+ Runtime::Env.jenkins_admin_username
+ end
+
+ def password
+ Runtime::Env.jenkins_admin_password
end
def host_address
@@ -16,30 +32,37 @@ module QA
end
def host_name
- if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped')
- 'localhost'
- end
+ return 'localhost' unless QA::Runtime::Env.running_in_ci?
super
end
def register!
+ authenticate_third_party
+
command = <<~CMD.tr("\n", ' ')
docker run -d --rm
--network #{network}
--hostname #{host_name}
--name #{@name}
- --env JENKINS_HOME=jenkins_home
+ --env JENKINS_USER=#{username}
+ --env JENKINS_PASS=#{password}
--publish #{@port}:8080
--publish 50000:50000
#{@image}
CMD
- if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped')
- command.gsub!("--network #{network} ", '')
- end
+ shell(command, mask_secrets: [password])
- shell command
+ wait_for_running
+ end
+
+ private
+
+ def wait_for_running
+ Support::Waiter.wait_until(max_duration: 10, reload_page: false, raise_on_failure: false) do
+ running?
+ end
end
end
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index 8563c3656a8..c332e7a6198 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -30,7 +30,7 @@ module QA
wait_until_shell_command_matches(dataloss_command, /Outdated repositories/)
end
- def replicated?(project_id)
+ def replicated?(project_id, project_name_prefix = 'gitaly_cluster')
Support::Retrier.retry_until(raise_on_failure: false) do
replicas = wait_until_shell_command(%(docker exec #{@gitlab} bash -c 'gitlab-rake "gitlab:praefect:replicas[#{project_id}]"')) do |line|
QA::Runtime::Logger.debug(line.chomp)
@@ -40,7 +40,7 @@ module QA
# ----------------------------------------------------------------------------------------------------------------------------------------------------------------
# gitaly_cluster-3aff1f2bd14e6c98 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619
#
- break line if line.start_with?('gitaly_cluster')
+ break line if line.start_with?(project_name_prefix)
break nil if line.include?('Something went wrong when getting replicas')
end
next false unless replicas
@@ -101,7 +101,7 @@ module QA
wait_until_shell_command_matches(
"docker inspect -f {{.State.Running}} #{name}",
/true/,
- sleep_interval: 3,
+ sleep_interval: 1,
max_duration: 180,
retry_on_exception: true
)
@@ -425,7 +425,7 @@ module QA
end
def value_for_node(data, node)
- data.find(-> {{ value: 0 }}) { |item| item[:node] == node }[:value]
+ data.find(-> { { value: 0 } }) { |item| item[:node] == node }[:value]
end
def wait_for_replication(project_id)
diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
index 79509bdbe01..df34bf32421 100644
--- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
@@ -65,10 +65,8 @@ module QA
end
def verify_repository_import
- expect(imported_project.api_response).to include(
- description: 'Project for github import test',
- import_error: nil
- )
+ expect(imported_project.reload!.description).to eq('Project for github import test')
+ expect(imported_project.api_response[:import_error]).to be_nil
end
def verify_commits_import
diff --git a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
index c47afbd23f0..de460a39ccf 100644
--- a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
@@ -8,9 +8,14 @@ module QA
describe 'Project import' do
let(:logger) { Runtime::Logger.logger }
let(:differ) { RSpec::Support::Differ.new(color: true) }
+ let(:gitlab_address) { QA::Runtime::Scenario.gitlab_address }
+ let(:dummy_url) { "https://example.com" }
let(:created_by_pattern) { /\*Created by: \S+\*\n\n/ }
let(:suggestion_pattern) { /suggestion:-\d+\+\d+/ }
+ let(:gh_link_pattern) { %r{https://github.com/#{github_repo}/(issues|pull)} }
+ let(:gl_link_pattern) { %r{#{gitlab_address}/#{imported_project.path_with_namespace}/-/(issues|merge_requests)} }
+ let(:event_pattern) { %r{(un)?assigned( to)? @\S+|mentioned in (issue|merge request) [!#]\d+|changed title from \*\*.*\*\* to \*\*.*\*\*} } # rubocop:disable Layout/LineLength
let(:api_client) { Runtime::API::Client.as_admin }
@@ -83,14 +88,14 @@ module QA
let(:gh_issue_comments) do
logger.debug("= Fetching issue comments =")
github_client.issues_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
- hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
end
end
let(:gh_pr_comments) do
logger.debug("= Fetching pr comments =")
github_client.pull_requests_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
- hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
+ hash[c.html_url.gsub(/\#\S+/, "")] << c.body&.gsub(gh_link_pattern, dummy_url) # use base html url as key
end
end
@@ -135,7 +140,7 @@ module QA
target: {
name: "GitLab",
project_name: imported_project.path_with_namespace,
- address: QA::Runtime::Scenario.gitlab_address,
+ address: gitlab_address,
data: {
branches: gl_branches.length,
commits: gl_commits.length,
@@ -381,15 +386,16 @@ module QA
end
logger.debug("Fetching comments for mr '#{mr[:title]}'")
+ comments = resource
+ .comments(auto_paginate: true, attempts: 2)
+ .reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
+
[mr[:iid], {
url: mr[:web_url],
title: mr[:title],
body: sanitize_description(mr[:description]) || '',
- comments: resource
- .comments(auto_paginate: true, attempts: 2)
- # remove system notes
- .reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
- .map { |c| sanitize_comment(c[:body]) }
+ events: events(comments),
+ comments: non_event_comments(comments)
}]
end.to_h
end
@@ -412,24 +418,54 @@ module QA
end
logger.debug("Fetching comments for issue '#{issue[:title]}'")
+ comments = resource.comments(auto_paginate: true, attempts: 2)
+
[issue[:iid], {
url: issue[:web_url],
title: issue[:title],
body: sanitize_description(issue[:description]) || '',
- comments: resource
- .comments(auto_paginate: true, attempts: 2)
- .map { |c| sanitize_comment(c[:body]) }
+ events: events(comments),
+ comments: non_event_comments(comments)
}]
end.to_h
end
end
- # Remove added prefixes and legacy diff format from comments
+ # Fetch comments without events
+ #
+ # @param [Array] comments
+ # @return [Array]
+ def non_event_comments(comments)
+ comments
+ .reject { |c| c[:body].match?(event_pattern) }
+ .map { |c| sanitize_comment(c[:body]) }
+ end
+
+ # Events
+ #
+ # @param [Array] comments
+ # @return [Array]
+ def events(comments)
+ comments
+ .select { |c| c[:body].match?(event_pattern) }
+ .map { |c| c[:body] }
+ end
+
+ # Normalize comments and make them directly comparable
+ #
+ # * remove created by prefixes
+ # * unify suggestion format
+ # * replace github and gitlab urls - some of the links to objects get transformed to gitlab entities, some don't,
+ # update all links to example.com for now
#
# @param [String] body
# @return [String]
def sanitize_comment(body)
- body.gsub(created_by_pattern, "").gsub(suggestion_pattern, "suggestion\r")
+ body
+ .gsub(created_by_pattern, "")
+ .gsub(suggestion_pattern, "suggestion\r")
+ .gsub(gl_link_pattern, dummy_url)
+ .gsub(gh_link_pattern, dummy_url)
end
# Remove created by prefix from descripion
diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
index fc221c963b1..874626e01f1 100644
--- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :requires_admin, :skip_live_env, except: { job: 'review-qa-*' } do
- describe 'rate limits' do
+ describe 'rate limits', :reliable do
let(:rate_limited_user) { Resource::User.fabricate_via_api! }
let(:api_client) { Runtime::API::Client.new(:gitlab, user: rate_limited_user) }
let!(:request) { Runtime::API::Request.new(api_client, '/users') }
diff --git a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
index faef321c89d..444d86f63d3 100644
--- a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
@@ -36,6 +36,7 @@ module QA
it(
'is allowed to push code to sub-group project via the CLI',
+ :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363345'
) do
expect do
@@ -52,6 +53,7 @@ module QA
it(
'is allowed to create a file in sub-group project via the API',
+ :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363348'
) do
expect do
@@ -68,6 +70,7 @@ module QA
it(
'is allowed to commit to sub-group project via the API',
+ :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349'
) do
expect do
@@ -116,6 +119,7 @@ module QA
it(
'is not allowed to push code to parent group project via the CLI',
+ :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363344'
) do
expect do
@@ -148,6 +152,7 @@ module QA
it(
'is not allowed to commit to parent group project via the API',
+ :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363342'
) do
expect do
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 cd1b7730fa9..5ee6dfdb8d8 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
@@ -67,6 +67,28 @@ module QA
it_behaves_like 'repository storage move'
end
+
+ # 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 Cluster to Gitaly', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369204' do
+ let(:source_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } }
+ let(:destination_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } }
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'repo-storage-move'
+ project.initialize_with_readme = true
+ project.repository_storage = source_storage[:name]
+ project.api_client = Runtime::API::Client.as_admin
+ end
+ end
+
+ before do
+ praefect_manager.gitlab = 'gitlab-gitaly-cluster'
+ end
+
+ it_behaves_like 'repository storage move'
+ end
end
end
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
index 150378016e1..2b96c35415e 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
@@ -20,11 +20,6 @@ module QA
praefect_manager.wait_for_replication(project.id)
end
- after do
- # Leave the cluster in a suitable state for subsequent tests
- praefect_manager.start_all_nodes
- end
-
it 'reads from each node', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347833' do
pre_read_data = praefect_manager.query_read_distribution
@@ -42,14 +37,12 @@ module QA
context 'when a node is unhealthy' do
before do
- praefect_manager.start_all_nodes
praefect_manager.stop_secondary_node
- praefect_manager.wait_for_secondary_node_health_check_failure
end
after do
# Leave the cluster in a suitable state for subsequent tests
- praefect_manager.start_all_nodes
+ praefect_manager.start_secondary_node
end
it 'does not read from the unhealthy node', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347834' do
diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
index b6296b5a263..a53614960cd 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
@@ -3,10 +3,7 @@
require 'parallel'
module QA
- RSpec.describe 'Create', quarantine: {
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361382',
- type: :investigating
- } do
+ RSpec.describe 'Create' do
context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env do
let(:praefect_manager) { Service::PraefectManager.new }
let(:project) do
@@ -35,6 +32,9 @@ module QA
# to `in_progress`, and create a job lock for each one.
queue_size_target = 10
+ # During normal operations we avoid create a replication event
+ # https://gitlab.com/groups/gitlab-org/-/epics/7741
+ praefect_manager.stop_secondary_node
Git::Repository.perform do |repository|
repository.uri = project.repository_http_location.uri
repository.use_default_credentials
@@ -46,11 +46,12 @@ module QA
end
repository.push_all_branches
end
+ praefect_manager.start_secondary_node
- count = 0
- while count < 1
+ Support::Retrier.retry_until(max_duration: 60) do
count = praefect_manager.replication_queue_lock_count
QA::Runtime::Logger.debug("Lock count: #{count}")
+ count >= 1
end
praefect_manager.stop_praefect
@@ -58,12 +59,12 @@ module QA
praefect_manager.start_praefect
- # Create a new project, push to it, and check that replication occurs
- project_push = Resource::Repository::ProjectPush.fabricate! do |push|
- push.project_name = "gitaly_cluster"
+ # Create a new project, and check that replication occurs
+ new_project = Resource::Project.fabricate! do |project|
+ project.initialize_with_readme = true
end
- expect(praefect_manager.replicated?(project_push.project.id)).to be true
+ expect(praefect_manager.replicated?(new_project.id, new_project.name)).to be true
end
end
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_repo_sync_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_repo_sync_spec.rb
index e27f37abedf..47be7e0620b 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/praefect_repo_sync_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_repo_sync_spec.rb
@@ -15,7 +15,6 @@ module QA
end
after do
- praefect_manager.start_all_nodes
praefect_manager.remove_repo_from_disk(repo1["relative_path"])
praefect_manager.remove_repo_from_disk(repo2["relative_path"])
praefect_manager.remove_repository_from_praefect_database(repo1["relative_path"])
diff --git a/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb b/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb
new file mode 100644
index 00000000000..98612d84b21
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.initialize_with_readme = true
+ end
+ end
+
+ context 'when creating a tag for a ref' do
+ context 'when it triggers a prereceive hook configured with a custom error' do
+ before do
+ # The configuration test prereceive hook must match a specific naming pattern
+ # In this test we create a project with a different name and then change the path.
+ # Otherwise we wouldn't be able create any commits to be tagged due to the hook.
+ project.change_path("project-reject-prereceive-#{SecureRandom.hex(8)}")
+ end
+
+ it 'returns a custom server hook error',
+ :skip_live_env,
+ except: { job: 'review-qa-*' },
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369053' do
+ expect { project.create_repository_tag('v1.2.3') }.to raise_error
+ .with_message(
+ /rejecting prereceive hook for projects with GL_PROJECT_PATH matching pattern reject-prereceive/
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb
index 79384e374d7..8412c0b2872 100644
--- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb
+++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb
@@ -3,7 +3,7 @@
require 'airborne'
module QA
- RSpec.describe 'Package', only: { subdomain: %i[staging pre] } do
+ RSpec.describe 'Package', :reliable, only: { subdomain: %i[staging staging-canary pre] } do
include Support::API
include Support::Helpers::MaskToken
@@ -41,7 +41,7 @@ module QA
stages:
- build
- test
-
+
build:
image: docker:19.03.12
stage: build
@@ -60,7 +60,7 @@ module QA
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
- docker pull $IMAGE_TAG
-
+
test:
image: dwdraju/alpine-curl-jq:latest
stage: test
@@ -72,7 +72,7 @@ module QA
- 'status_code=$(curl --request DELETE --head --output /dev/null --write-out "%{http_code}\n" --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories/$id/tags/master")'
- if [ $status_code -ne 200 ]; then exit 1; fi;
- 'status_code=$(curl --head --output /dev/null --write-out "%{http_code}\n" --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories/$id/tags/master")'
- - if [ $status_code -ne 404 ]; then exit 1; fi;
+ - if [ $status_code -ne 404 ]; then exit 1; fi;
YAML
end
diff --git a/qa/qa/specs/features/api/8_monitor/metrics_spec.rb b/qa/qa/specs/features/api/8_monitor/metrics_spec.rb
new file mode 100644
index 00000000000..1235b996958
--- /dev/null
+++ b/qa/qa/specs/features/api/8_monitor/metrics_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'GitLab Metrics', :aggregate_failures, :orchestrated, :metrics do
+ let(:web_uri) { URI.parse(Runtime::Scenario.gitlab_address) }
+ let(:endpoint) do
+ "#{web_uri.scheme}://#{web_uri.host}:#{port}#{path}"
+ end
+
+ let(:response) { RestClient.get(endpoint) }
+
+ describe 'Web metrics' do
+ describe 'via Rails controller endpoint' do
+ let(:port) { web_uri.port }
+ let(:path) { '/-/metrics' }
+
+ it 'returns 200 OK and serves metrics',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/362911' do
+ # This does not currently work because it requires a special auth token to
+ # make an internal endpoint request. But we should probably test this, too.
+ skip
+ end
+ end
+
+ describe 'via dedicated server' do
+ let(:port) { '8083' }
+ let(:path) { '/metrics' }
+
+ it 'returns 200 OK and serves metrics',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/362912' do
+ expect(response.code).to be(200)
+ expect(response.body).to match(/^puma_/)
+ end
+ end
+ end
+
+ describe 'Sidekiq metrics' do
+ describe 'via dedicated server' do
+ let(:port) { '8082' }
+ let(:path) { '/metrics' }
+
+ it 'returns 200 OK and serves metrics',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/362913' do
+ expect(response.code).to be(200)
+ expect(response.body).to match(/^sidekiq_/)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb
index 10076a329bc..867c54102ae 100644
--- a/qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Product Intelligence' do
+ RSpec.describe 'Analytics' do
describe 'Performance bar display', :requires_admin, :skip_live_env do
context 'when logged in as an admin user' do
# performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided)
diff --git a/qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_default_enabled_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb
index cc2888063ca..7826aca3601 100644
--- a/qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_default_enabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Product Intelligence' do
+ RSpec.describe 'Analytics' do
describe 'Service ping default enabled' do
context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
before do
diff --git a/qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_disabled_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb
index f762adf52a1..8b30d6a7ad7 100644
--- a/qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_disabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Product Intelligence' do
+ RSpec.describe 'Analytics' do
describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do
context 'when disabled from gitlab.yml config' do
before do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index db02d1e8390..2c331584cf7 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -9,7 +9,7 @@ module QA
end
end
- let(:target_group) do
+ let!(:target_group) do
Resource::Group.fabricate_via_api! do |group|
group.path = "target-group-for-transfer_#{SecureRandom.hex(8)}"
end
@@ -19,48 +19,39 @@ module QA
Resource::Project.fabricate_via_api! do |project|
project.group = source_group
project.name = 'transfer-project'
- project.initialize_with_readme = true
end
end
- let(:edited_readme_content) { 'Here is the edited content.' }
+ let(:readme_content) { 'Here is the edited content.' }
before do
- Flow::Login.sign_in
-
- project.visit!
-
- Page::Project::Show.perform do |project|
- project.click_file('README.md')
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.add_files([
+ { file_path: 'README.md', content: readme_content }
+ ])
end
- Page::File::Show.perform(&:click_edit)
+ Flow::Login.sign_in
- Page::File::Edit.perform do |file|
- file.remove_content
- file.add_content(edited_readme_content)
- file.commit_changes
- end
+ project.visit!
end
it 'user transfers a project between groups',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347878' do
- # Retry is needed here as the target group is not avaliable for transfer right away.
- QA::Support::Retrier.retry_on_exception(reload_page: page) do
- Page::File::Show.perform(&:go_to_general_settings)
+ Page::File::Show.perform(&:go_to_general_settings)
- Page::Project::Settings::Main.perform(&:expand_advanced_settings)
+ Page::Project::Settings::Main.perform(&:expand_advanced_settings)
- Page::Project::Settings::Advanced.perform do |advanced|
- advanced.transfer_project!(project.name, target_group.full_path)
- end
+ Page::Project::Settings::Advanced.perform do |advanced|
+ advanced.transfer_project!(project.name, target_group.full_path)
end
Page::Project::Settings::Main.perform(&:click_project)
Page::Project::Show.perform do |project|
expect(project).to have_breadcrumb(target_group.path)
- expect(project).to have_readme_content(edited_readme_content)
+ expect(project).to have_readme_content(readme_content)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb
index f85c07001e2..6c69e4c59d9 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Manage', :requires_admin, :skip_live_env do
+ context 'Manage', :reliable, :requires_admin, :skip_live_env do
describe '2FA' do
let!(:user) { Resource::User.fabricate_via_api! }
let!(:user_api_client) { Runtime::API::Client.new(:gitlab, user: user) }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb
index 2c7656e20f1..1d30b915594 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', only: { subdomain: :staging }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/344213', type: :stale } do
+ RSpec.describe 'Manage', only: { subdomain: %i[staging staging-canary] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/344213', type: :stale } do
describe 'basic user' do
it 'remains logged in when redirected from canary to non-canary node', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347626' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 85ab466078a..8cc772ed022 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
@@ -2,7 +2,7 @@
module QA
RSpec.shared_examples 'registration and login' do
- it 'allows the user to registers and login' do
+ it 'allows the user to register and login' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Resource::User.fabricate_via_browser_ui!
@@ -39,7 +39,7 @@ module QA
end
end
- describe 'standard', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347867' do
+ describe 'standard', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347867' do
context 'when admin approval is not required' do
before(:all) do
set_require_admin_approval_after_user_signup_via_api(false)
@@ -69,7 +69,7 @@ module QA
Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 3) { !user.exists? }
end
- it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
+ it 'allows recreating with same credentials', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
expect(Page::Main::Menu.perform(&:signed_in?)).to be_falsy
Flow::Login.sign_in(as: user, skip_page_validation: true)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
index 3921595204c..f624f2fb44f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
@@ -42,10 +42,6 @@ module QA
expect(project.asset_exists?(expected_badge_image_url)).to be_truthy
end
end
-
- after do
- project&.remove_via_api!
- end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 0063ce2613a..d07fff80b19 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -10,7 +10,6 @@ module QA
expect(project_page).to have_content(
/Project \S?#{project_name}\S+ was successfully created/
)
- expect(project_page).to have_content('create awesome project test')
expect(project_page).to have_content('The repository for this project is empty')
end
end
@@ -26,7 +25,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_browser_ui! do |project|
project.name = project_name
- project.description = 'create awesome project test'
+ project.description = nil
end
end
@@ -38,17 +37,13 @@ module QA
let(:project) do
Resource::Project.fabricate_via_browser_ui! do |project|
project.name = project_name
- project.description = 'create awesome project test'
project.personal_namespace = Runtime::User.username
+ project.description = nil
end
end
it_behaves_like 'successful project creation'
end
-
- after do
- project.remove_via_api!
- end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
index 8201e2772aa..dbfb114dc82 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Invite group' do
+ describe 'Invite group', :reliable do
shared_examples 'invites group to project' do
it 'verifies group is added and members can access project with correct access level' do
Page::Project::Menu.perform(&:click_members)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
index 2f148c4051c..29e590976d2 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_owner_permissions_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Project owner permissions' do
+ describe 'Project owner permissions', :reliable do
let!(:owner) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/impersonation_token_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/impersonation_token_spec.rb
new file mode 100644
index 00000000000..5bcea1ff094
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/impersonation_token_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'Impersonation tokens', :requires_admin do
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+
+ let!(:user) do
+ Resource::User.fabricate_via_api! do |usr|
+ usr.api_client = admin_api_client
+ usr.hard_delete_on_api_removal = true
+ end
+ end
+
+ it(
+ 'can be created and revoked via the UI',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368888'
+ ) do
+ impersonation_token = QA::Resource::ImpersonationToken.fabricate_via_browser_ui! do |impersonation_token|
+ impersonation_token.api_client = admin_api_client
+ impersonation_token.user = user
+ end
+
+ expect(impersonation_token.token).not_to be_nil
+
+ impersonation_token.revoke_via_browser_ui!
+
+ expect(page).to have_text("Revoked impersonation token #{impersonation_token.name}!")
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb
index 71415c4bb57..66208921f0e 100644
--- a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: {
+ RSpec.describe 'Plan', quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839',
type: :test_environment,
only: { job: 'review-qa-*' }
} do
- context 'Design Management' do
+ describe 'Design Management' do
let(:issue) { Resource::Issue.fabricate_via_api! }
let(:design_filename) { 'banana_sample.gif' }
let(:design) { File.absolute_path(File.join('qa', 'fixtures', 'designs', design_filename)) }
@@ -16,7 +16,8 @@ module QA
Flow::Login.sign_in
end
- it 'user adds a design and annotates it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do
+ it 'user adds a design and annotates it',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do
issue.visit!
Page::Project::Issue::Show.perform do |issue|
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/2_plan/design_management/archive_design_content_spec.rb
index 9b969f563a2..8cbc6d7209c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: {
+ RSpec.describe 'Plan', quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839',
type: :test_environment,
only: { job: 'review-qa-*' }
} do
- context 'Design Management' do
+ describe 'Design Management' do
let(:first_design) { Resource::Design.fabricate! }
let(:second_design) do
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/2_plan/design_management/modify_design_content_spec.rb
index 6a0c51245ad..8f4902026d2 100644
--- a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: {
+ RSpec.describe 'Plan', quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839',
type: :test_environment,
only: { job: 'review-qa-*' }
} do
- context 'Design Management' do
+ describe 'Design Management' do
let(:design) do
Resource::Design.fabricate_via_browser_ui! do |design|
design.filename = 'testfile.png'
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 b8f1824126d..ed271533f87 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
@@ -1,11 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe(
- 'Plan',
- :smoke,
- quarantine: { issue: 'https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7099', type: :investigating, only: { subdomain: 'pre' } }
- ) do
+ RSpec.describe 'Plan', :smoke do
describe 'Issue creation' do
let(:project) { Resource::Project.fabricate_via_api! }
let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
index 36b7378ee2a..206e6b8a456 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Plan', :reliable do
describe 'Custom issue templates' do
- let(:template_name) { 'custom_issue_template'}
+ let(:template_name) { 'custom_issue_template' }
let(:template_content) { 'This is a custom issue template test' }
let(:template_project) do
diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
index ea531d84634..4bfd253c992 100644
--- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
@@ -1,8 +1,23 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/195179', type: :flaky } do
+ RSpec.describe 'Create', :requires_admin, :skip_live_env, except: { job: 'review-qa-*' } do
describe 'Jenkins integration' do
+ let(:jenkins_server) { Service::DockerRun::Jenkins.new }
+
+ let(:jenkins_client) do
+ Vendor::Jenkins::Client.new(
+ jenkins_server.host_name,
+ port: jenkins_server.port,
+ user: Runtime::Env.jenkins_admin_username,
+ password: Runtime::Env.jenkins_admin_password
+ )
+ end
+
+ let(:jenkins_project_name) { "gitlab_jenkins_#{SecureRandom.hex(5)}" }
+
+ let(:connection_name) { 'gitlab-connection' }
+
let(:project_name) { "project_with_jenkins_#{SecureRandom.hex(4)}" }
let(:project) do
@@ -13,97 +28,82 @@ module QA
end
end
- before do
- jenkins_server = run_jenkins_server
+ let(:access_token) do
+ Runtime::Env.personal_access_token ||= fabricate_access_token
+ end
- Vendor::Jenkins::Page::Base.host = jenkins_server.host_address
+ before do
+ toggle_local_requests(true)
+ jenkins_server.register!
- Runtime::Env.personal_access_token ||= fabricate_personal_access_token
+ Support::Waiter.wait_until(max_duration: 30, reload_page: false, retry_on_exception: true) do
+ jenkins_client.ready?
+ end
- allow_requests_to_local_networks
+ configure_gitlab_jenkins
+ end
- setup_jenkins
+ after do
+ jenkins_server&.remove!
+ toggle_local_requests(false)
end
it 'integrates and displays build status for MR pipeline in GitLab', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347788' do
- login_to_gitlab
+ setup_project_integration
- setup_project_integration_with_jenkins
+ jenkins_integration = project.find_integration('jenkins')
+ expect(jenkins_integration).not_to be(nil), 'Jenkins integration did not save'
+ expect(jenkins_integration[:active]).to be(true), 'Jenkins integration is not active'
- expect(page).to have_text("Jenkins settings saved and active.")
+ job = create_jenkins_job
- QA::Support::Retrier.retry_on_exception do
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
- push.new_branch = false
- push.file_name = "file_#{SecureRandom.hex(4)}.txt"
- end
-
- Vendor::Jenkins::Page::LastJobConsole.perform do |job_console|
- job_console.job_name = project_name
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.new_branch = false
+ push.file_name = "file_#{SecureRandom.hex(4)}.txt"
+ end
- job_console.visit!
+ Support::Waiter.wait_until(max_duration: 60, raise_on_failure: false, reload_page: false) do
+ job.status == :success
+ end
- Support::Waiter.wait_until(sleep_interval: 2, reload_page: page) do
- job_console.has_successful_build? && job_console.no_failed_status_update?
- end
- end
+ expect(job.status).to eql(:success), "Build failed or is not found: #{job.log}"
- project.visit!
+ project.visit!
- Flow::Pipeline.visit_latest_pipeline
+ Flow::Pipeline.visit_latest_pipeline
- Page::Project::Pipeline::Show.perform do |show|
- expect(show).to have_build('jenkins', status: :success, wait: 15)
- end
+ Page::Project::Pipeline::Show.perform do |show|
+ expect(show).to have_build('jenkins', status: :success, wait: 15)
end
end
- after do
- remove_jenkins_server
- end
+ private
- def setup_jenkins
- Vendor::Jenkins::Page::Login.perform do |login_page|
- login_page.visit!
- login_page.login
- end
-
- token_description = "token-#{SecureRandom.hex(8)}"
-
- Vendor::Jenkins::Page::NewCredentials.perform do |new_credentials|
- new_credentials.visit_and_set_gitlab_api_token(Runtime::Env.personal_access_token, token_description)
- end
-
- Vendor::Jenkins::Page::Configure.perform do |configure|
- configure.visit_and_setup_gitlab_connection(patch_host_name(Runtime::Scenario.gitlab_address, 'gitlab'), token_description) do
- configure.click_test_connection
- expect(configure).to have_success
- end
- end
+ def setup_project_integration
+ login_to_gitlab
- Vendor::Jenkins::Page::NewJob.perform do |new_job|
- new_job.visit_and_create_new_job_with_name(project_name)
- end
+ project.visit!
- Vendor::Jenkins::Page::ConfigureJob.perform do |configure_job|
- configure_job.job_name = project_name
- configure_job.configure(scm_url: patch_host_name(project.repository_http_location.git_uri, 'gitlab'))
- end
- end
+ Page::Project::Menu.perform(&:click_project)
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link)
- def run_jenkins_server
- Service::DockerRun::Jenkins.new.tap do |runner|
- runner.pull
- runner.register!
+ QA::Page::Project::Settings::Services::Jenkins.perform do |jenkins|
+ jenkins.setup_service_with(
+ jenkins_url: patch_host_name(jenkins_server.host_address, 'jenkins-server'),
+ project_name: jenkins_project_name,
+ username: jenkins_server.username,
+ password: jenkins_server.password
+ )
end
end
- def remove_jenkins_server
- Service::DockerRun::Jenkins.new.remove!
+ def login_to_gitlab
+ Flow::Login.sign_in
end
- def fabricate_personal_access_token
+ def fabricate_access_token
login_to_gitlab
token = Resource::PersonalAccessToken.fabricate!.token
@@ -111,8 +111,23 @@ module QA
token
end
- def login_to_gitlab
- Flow::Login.sign_in
+ def create_jenkins_job
+ jenkins_client.create_job jenkins_project_name do |job|
+ job.gitlab_connection = connection_name
+ job.description = 'Just a job'
+ job.repo_url = patch_host_name(project.repository_http_location.git_uri, 'gitlab')
+ job.shell_command = 'sleep 5'
+ end
+ end
+
+ def configure_gitlab_jenkins
+ jenkins_client.configure_gitlab_plugin(
+ patch_host_name(Runtime::Scenario.gitlab_address, 'gitlab'),
+ connection_name: connection_name,
+ access_token: access_token,
+ read_timeout: 20,
+ connection_timeout: 10
+ )
end
def patch_host_name(host_name, container_name)
@@ -122,32 +137,8 @@ module QA
host_name.gsub('localhost', ip_address)
end
- def setup_project_integration_with_jenkins
- project.visit!
-
- Page::Project::Menu.perform(&:click_project)
- Page::Project::Menu.perform(&:go_to_integrations_settings)
- Page::Project::Settings::Integrations.perform(&:click_jenkins_ci_link)
-
- QA::Page::Project::Settings::Services::Jenkins.perform do |jenkins|
- jenkins.setup_service_with(jenkins_url: patch_host_name(Vendor::Jenkins::Page::Base.host, 'jenkins-server'),
- project_name: project_name)
- end
- end
-
- def allow_requests_to_local_networks
- Page::Main::Menu.perform(&:sign_out_if_signed_in)
- Flow::Login.sign_in_as_admin
- Page::Main::Menu.perform(&:go_to_admin_area)
- Page::Admin::Menu.perform(&:go_to_network_settings)
-
- Page::Admin::Settings::Network.perform do |network|
- network.expand_outbound_requests do |outbound_requests|
- outbound_requests.allow_requests_to_local_network_from_services
- end
- end
-
- Page::Main::Menu.perform(&:sign_out)
+ def toggle_local_requests(on)
+ Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: on)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
index 3373f4f4233..6ce4217f8ac 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create', :reliable do
describe 'Merge request custom templates' do
- let(:template_name) { 'custom_merge_request_template'}
+ let(:template_name) { 'custom_merge_request_template' }
let(:template_content) { 'This is a custom merge request template test' }
let(:template_project) do
Resource::Project.fabricate_via_api! do |project|
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
index d73fb57a581..aa637ac4d55 100644
--- 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
@@ -52,7 +52,12 @@ module QA
merge_request.click_commits_tab
- expect(merge_request).to have_content(commit_message)
+ # Commit does not always display immediately and may require a page refresh
+ # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/368735
+ # TODO: Remove page refresh logic once issue is resolved.
+ Support::Retrier.retry_on_exception(max_attempts: 2, reload_page: merge_request) do
+ expect(merge_request).to have_content(commit_message)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/pages/pages_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb
index d1d2340e5f1..191c4a096e7 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pages/pages_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/pages/pages_pipeline_spec.rb
@@ -31,7 +31,8 @@ module QA
pipeline.visit!
end
- it 'runs a Pages-specific pipeline', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347669' do
+ it('runs a Pages-specific pipeline',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347669') do
Page::Project::Pipeline::Show.perform do |show|
expect(show).to have_job(:pages)
show.click_job(:pages)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb
index 8074e1fa992..3db8128bc6d 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/license_detecton_spec.rb
@@ -28,16 +28,16 @@ module QA
context 'on a project with a commonly used LICENSE',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366842' do
it_behaves_like 'project license detection' do
- let(:license_file_name) {'bsd-3-clause'}
- let(:rendered_license_name) {'BSD 3-Clause "New" or "Revised" License'}
+ let(:license_file_name) { 'bsd-3-clause' }
+ let(:rendered_license_name) { 'BSD 3-Clause "New" or "Revised" License' }
end
end
context 'on a project with a less commonly used LICENSE',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366843' do
it_behaves_like 'project license detection' do
- let(:license_file_name) {'GFDL-1.2-only'}
- let(:rendered_license_name) {'Other'}
+ let(:license_file_name) { 'GFDL-1.2-only' }
+ let(:rendered_license_name) { 'Other' }
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb
index f6448fea2d4..fb87ca864f4 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage' do
+ RSpec.describe 'Create' do
describe 'Repository tags', :reliable do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -104,11 +104,8 @@ module QA
end
def add_members_to_project(project)
- @developer_user = developer_user
- @maintainer_user = maintainer_user
-
- project.add_member(@developer_user, Resource::Members::AccessLevel::DEVELOPER)
- project.add_member(@maintainer_user, Resource::Members::AccessLevel::MAINTAINER)
+ project.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER)
+ project.add_member(maintainer_user, Resource::Members::AccessLevel::MAINTAINER)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb
index f374ecff3f2..55df1615f5c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb
@@ -3,7 +3,8 @@
module QA
RSpec.describe 'Create' do
describe 'SSH keys support', :smoke do
- key_title = "key for ssh tests #{Time.now.to_f}"
+ let(:key_title) { "key for ssh tests #{Time.now.to_f}" }
+
key = nil
before do
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 b4519327a62..620e6870b26 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, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/326624', type: :investigating } do
+ RSpec.describe 'Create', :smoke do
describe 'Personal snippet creation' do
let(:snippet) do
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
diff --git a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
new file mode 100644
index 00000000000..f95f624d59a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+# tagged transient due to feature-flag caching flakiness. Remove tag along with feature flag removal.
+module QA
+ RSpec.describe 'Create', feature_flag: { name: 'source_editor_toolbar', scope: :global } do
+ describe 'Source editor toolbar preview' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'empty-project-with-md'
+ project.initialize_with_readme = true
+ end
+ end
+
+ let(:edited_readme_content) { 'Here is the edited content.' }
+
+ before do
+ Runtime::Feature.enable(:source_editor_toolbar)
+ Flow::Login.sign_in
+ end
+
+ after do
+ Runtime::Feature.disable(:source_editor_toolbar)
+ end
+
+ it 'can preview markdown side-by-side while editing',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367749' do
+ project.visit!
+ Page::Project::Show.perform do |project|
+ project.click_file('README.md')
+ end
+
+ Page::File::Show.perform(&:click_edit)
+
+ # wait_until required due to feature_caching. Remove along with feature flag removal.
+ Page::File::Edit.perform do |file|
+ Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page,
+ retry_on_exception: true) do
+ expect(file).to have_element(:editor_toolbar_button)
+ end
+ file.remove_content
+ file.click_editor_toolbar
+ file.add_content('# ' + edited_readme_content)
+ file.wait_for_markdown_preview('h1', edited_readme_content)
+ file.commit_changes
+ end
+
+ Page::File::Show.perform do |file|
+ aggregate_failures 'file details' do
+ expect(file).to have_notice('Your changes have been successfully committed.')
+ expect(file).to have_file_content(edited_readme_content)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb
index 02ee94381b2..bef15b46fcd 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb
@@ -2,12 +2,7 @@
module QA
RSpec.describe 'Verify' do
- # TODO: Remove this test when feature flag is removed
- # Flag rollout issue https://gitlab.com/gitlab-org/gitlab/-/issues/364257
- describe 'Pipeline editor', :reliable, feature_flag: {
- name: :simulate_pipeline,
- scope: :global
- } do
+ describe 'Pipeline editor', :reliable do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pipeline-editor-project'
@@ -42,19 +37,20 @@ module QA
end
before do
- Runtime::Feature.disable(:simulate_pipeline) if Runtime::Feature.enabled?(:simulate_pipeline)
+ # Make sure a pipeline is created before visiting pipeline editor page.
+ # Otherwise, test might timeout before the page finishing fetching pipeline status.
+ Support::Waiter.wait_until { project.pipelines.present? }
Flow::Login.sign_in
project.visit!
Page::Project::Menu.perform(&:go_to_pipeline_editor)
end
- after do
- project&.remove_via_api!
- end
-
context 'when CI has valid syntax' do
- it 'shows valid validations', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349128' do
+ it(
+ 'shows valid validations',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368332'
+ ) do
Page::Project::PipelineEditor::Show.perform do |show|
aggregate_failures do
expect(show.ci_syntax_validate_message).to have_content('Pipeline syntax is correct')
@@ -65,8 +61,9 @@ module QA
expect(show).to have_job(job), "Pipeline graph does not have job #{job}."
end
- show.go_to_lint_tab
- expect(show.tab_alert_message).to have_content('Syntax is correct')
+ show.go_to_validate_tab
+ show.simulate_pipeline
+ expect(show.tab_alert_title).to have_content('Simulation completed successfully')
show.go_to_view_merged_yaml_tab
expect(show).to have_source_editor
@@ -76,7 +73,10 @@ module QA
end
context 'when CI has invalid syntax' do
- it 'shows invalid validations', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349129' do
+ it(
+ 'shows invalid validations',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368333'
+ ) do
invalid_msg = 'syntax is invalid'
Page::Project::PipelineEditor::Show.perform do |show|
@@ -86,8 +86,9 @@ module QA
show.go_to_visualize_tab
expect(show.tab_alert_message).to have_content(invalid_msg)
- show.go_to_lint_tab
- expect(show.tab_alert_message).to have_content('Syntax is incorrect')
+ show.go_to_validate_tab
+ show.simulate_pipeline
+ expect(show.tab_alert_title).to have_content('Pipeline simulation completed with errors')
show.go_to_view_merged_yaml_tab
expect(show.tab_alert_message).to have_content(invalid_msg)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb
new file mode 100644
index 00000000000..412498476f0
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+module QA
+ # TODO: remove feature flag upon rollout completion
+ # FF rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/363186
+ RSpec.describe 'Verify', :runner, feature_flag: {
+ name: 'ci_docker_image_pull_policy',
+ scope: :global
+ } do
+ describe 'Pipeline with image:pull_policy' do
+ let(:runner_name) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+ let(:job_name) { "test-job-#{pull_policies.join('-')}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'pipeline-with-image-pull-policy'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = runner_name
+ runner.tags = [runner_name]
+ runner.executor = :docker
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(:ci_docker_image_pull_policy)
+ # Give the feature some time to switch
+ sleep(30)
+
+ update_runner_policy(allowed_policies)
+ add_ci_file
+ Flow::Login.sign_in
+ project.visit!
+ Flow::Pipeline.visit_latest_pipeline
+ end
+
+ after do
+ Runtime::Feature.disable(:ci_docker_image_pull_policy)
+
+ runner.remove_via_api!
+ end
+
+ context 'when policy is allowed' do
+ let(:allowed_policies) { %w[if-not-present always never] }
+
+ where do
+ {
+ 'with [always] policy' => {
+ pull_policies: %w[always],
+ pull_image: true,
+ message: 'Pulling docker image ruby:2.6',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367154'
+ },
+ 'with [always if-not-present] policies' => {
+ pull_policies: %w[always if-not-present],
+ pull_image: true,
+ message: 'Pulling docker image ruby:2.6',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368857'
+ },
+ 'with [if-not-present] policy' => {
+ pull_policies: %w[if-not-present],
+ pull_image: true,
+ message: 'Using locally found image version due to "if-not-present" pull policy',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368858'
+ },
+ 'with [never] policy' => {
+ pull_policies: %w[never],
+ pull_image: false,
+ message: 'Pulling docker image',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368859'
+ }
+ }
+ end
+
+ with_them do
+ it 'applies pull policy in job correctly', testcase: params[:testcase] do
+ visit_job
+
+ if pull_image
+ expect(job_log).to have_content(message),
+ "Expected to find #{message} in #{job_log}, but didn't."
+ else
+ expect(job_log).not_to have_content(message),
+ "Found #{message} in #{job_log}, but didn't expect to."
+ end
+ end
+ end
+ end
+
+ context 'when policy is not allowed' do
+ let(:allowed_policies) { %w[never] }
+ let(:pull_policies) { %w[always] }
+
+ let(:message) do
+ 'ERROR: Preparation failed: the configured PullPolicies ([always])'\
+ ' are not allowed by AllowedPullPolicies ([never])'
+ end
+
+ it(
+ 'fails job with policy not allowed message',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368853'
+ ) do
+ visit_job
+
+ expect(job_log).to have_content(message),
+ "Expected to find #{message} in #{job_log}, but didn't."
+ end
+ end
+
+ private
+
+ def update_runner_policy(allowed_policies)
+ Runtime::Logger.info('Updating runner config to allow pull policies...')
+
+ # Copy config.toml file from docker to local
+ # Update local file with allowed_pull_policies config
+ # Copy file with new content back to docker
+ tempdir = Tempfile.new('config.toml')
+ QA::Service::Shellout.shell("docker cp #{runner_name}:/etc/gitlab-runner/config.toml #{tempdir.path}")
+
+ File.open(tempdir.path, 'a') do |f|
+ f << %Q[ allowed_pull_policies = #{allowed_policies}\n]
+ end
+
+ QA::Service::Shellout.shell("docker cp #{tempdir.path} #{runner_name}:/etc/gitlab-runner/config.toml")
+
+ tempdir.close!
+
+ # Give runner some time to pick up new configuration
+ sleep(30)
+ end
+
+ def add_ci_file
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ default:
+ image: ruby:2.6
+ tags: [#{runner_name}]
+
+ #{job_name}:
+ script: echo "Using pull policies #{pull_policies}"
+ image:
+ name: ruby:2.6
+ pull_policy: #{pull_policies}
+ YAML
+ }
+ ]
+ )
+ end
+ end
+
+ def visit_job
+ Page::Project::Pipeline::Show.perform do |show|
+ Support::Waiter.wait_until { show.completed? }
+
+ show.click_job(job_name)
+ end
+ end
+
+ def job_log
+ Page::Project::Job::Show.perform(&:output)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
index 6918c087a4e..5a29b44e8b3 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Package' do
- describe 'Container Registry', :reliable, only: { subdomain: %i[staging pre] } do
+ describe 'Container Registry', only: { subdomain: %i[staging staging-canary pre] } do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-registry'
@@ -37,7 +37,7 @@ module QA
do
docker info && break
sleep 1s
- done
+ done
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
index d9d75a8ae7a..ad820977747 100644
--- a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
@@ -4,6 +4,7 @@ module QA
RSpec.describe 'Package', :orchestrated, :registry, only: { pipeline: :main } do
describe 'Dependency Proxy' do
using RSpec::Parameterized::TableSyntax
+ include Support::Helpers::MaskToken
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -21,6 +22,19 @@ module QA
end
end
+ let(:group_deploy_token) do
+ Resource::GroupDeployToken.fabricate_via_api! do |deploy_token|
+ deploy_token.name = 'dp-group-deploy-token'
+ deploy_token.group = project.group
+ deploy_token.scopes = %w[
+ read_registry
+ write_registry
+ ]
+ end
+ end
+
+ let(:personal_access_token) { Runtime::Env.personal_access_token }
+
let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) }
let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" }
let(:dependency_proxy_url) { "#{gitlab_host_with_port}/#{project.group.full_path}/dependency_proxy/containers" }
@@ -43,12 +57,92 @@ module QA
runner.remove_via_api!
end
- where(:case_name, :docker_client_version, :testcase) do
- 'using docker:19.03.12' | 'docker:19.03.12' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347605'
- 'using docker:20.10' | 'docker:20.10' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347604'
+ where do
+ {
+ 'using docker:18.09.9 and a personal access token' => {
+ docker_client_version: 'docker:18.09.9',
+ authentication_token_type: :personal_access_token,
+ token_name: 'Personal Access Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370195'
+ },
+ 'using docker:18.09.9 and a group deploy token' => {
+ docker_client_version: 'docker:18.09.9',
+ authentication_token_type: :group_deploy_token,
+ token_name: 'Deploy Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370196'
+ },
+ 'using docker:18.09.9 and a ci job token' => {
+ docker_client_version: 'docker:18.09.9',
+ authentication_token_type: :ci_job_token,
+ token_name: 'Job Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370198'
+ },
+ 'using docker:19.03.12 and a personal access token' => {
+ docker_client_version: 'docker:19.03.12',
+ authentication_token_type: :personal_access_token,
+ token_name: 'Personal Access Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370189'
+ },
+ 'using docker:19.03.12 and a group deploy token' => {
+ docker_client_version: 'docker:19.03.12',
+ authentication_token_type: :group_deploy_token,
+ token_name: 'Deploy Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370190'
+ },
+ 'using docker:19.03.12 and a ci job token' => {
+ docker_client_version: 'docker:19.03.12',
+ authentication_token_type: :ci_job_token,
+ token_name: 'Job Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370191'
+ },
+ 'using docker:20.10 and a personal access token' => {
+ docker_client_version: 'docker:20.10',
+ authentication_token_type: :personal_access_token,
+ token_name: 'Personal Access Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370192'
+ },
+ 'using docker:20.10 and a group deploy token' => {
+ docker_client_version: 'docker:20.10',
+ authentication_token_type: :group_deploy_token,
+ token_name: 'Deploy Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370193'
+ },
+ 'using docker:20.10 and a ci job token' => {
+ docker_client_version: 'docker:20.10',
+ authentication_token_type: :ci_job_token,
+ token_name: 'Job Token',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370194'
+ }
+ }
end
with_them do
+ let(:auth_token) do
+ case authentication_token_type
+ when :personal_access_token
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project)
+ when :group_deploy_token
+ use_group_ci_variable(
+ name: "GROUP_DEPLOY_TOKEN_#{group_deploy_token.id}",
+ value: group_deploy_token.token,
+ group: project.group
+ )
+ when :ci_job_token
+ '$CI_JOB_TOKEN'
+ end
+ end
+
+ let(:auth_user) do
+ case authentication_token_type
+ when :personal_access_token
+ "$CI_REGISTRY_USER"
+ when :group_deploy_token
+ "\"#{group_deploy_token.username}\""
+ when :ci_job_token
+ 'gitlab-ci-token'
+ end
+ end
+
it "pulls an image using the dependency proxy", testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
@@ -65,8 +159,7 @@ module QA
command: ["--insecure-registry=gitlab.test:80"]
before_script:
- apk add curl jq grep
- - echo $CI_DEPENDENCY_PROXY_SERVER
- - docker login -u "$CI_DEPENDENCY_PROXY_USER" -p "$CI_DEPENDENCY_PROXY_PASSWORD" gitlab.test:80
+ - docker login -u #{auth_user} -p #{auth_token} gitlab.test:80
script:
- docker pull #{dependency_proxy_url}/#{image_sha}
- TOKEN=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq --raw-output .token)
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
index 980c6da2576..608dd7e089f 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Configure' do
- describe 'AutoDevOps Templates', only: { subdomain: :staging } do
+ describe 'AutoDevOps Templates', only: { subdomain: %i[staging staging-canary] } do
using RSpec::Parameterized::TableSyntax
# specify jobs to be disabled in the pipeline.
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index dca6f961047..f1a2eb71390 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Configure',
- only: { subdomain: :staging },
+ only: { subdomain: %i[staging staging-canary] },
quarantine: {
issue: 'https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1198',
type: :waiting_on
diff --git a/qa/qa/specs/features/sanity/feature_flags_spec.rb b/qa/qa/specs/features/sanity/feature_flags_spec.rb
index f771978802e..acb9528fe6a 100644
--- a/qa/qa/specs/features/sanity/feature_flags_spec.rb
+++ b/qa/qa/specs/features/sanity/feature_flags_spec.rb
@@ -26,7 +26,7 @@ module QA
end
let(:flag) { Pathname.new(file.path).basename('.yml').to_s }
- let(:root) { '..'}
+ let(:root) { '..' }
before do
definition = <<~YAML
@@ -78,7 +78,7 @@ module QA
end
context 'with an EE feature flag' do
- let(:root) { '../ee'}
+ let(:root) { '../ee' }
include_examples 'gets flag value'
end
diff --git a/qa/qa/support/helpers/mask_token.rb b/qa/qa/support/helpers/mask_token.rb
index 1f8161f7173..0c0af524c97 100644
--- a/qa/qa/support/helpers/mask_token.rb
+++ b/qa/qa/support/helpers/mask_token.rb
@@ -13,6 +13,16 @@ module QA
end
"$#{name}"
end
+
+ def use_group_ci_variable(name:, value:, group:)
+ Resource::GroupCiVariable.fabricate_via_api! do |ci_variable|
+ ci_variable.group = group
+ ci_variable.key = name
+ ci_variable.value = value
+ ci_variable.protected = true
+ end
+ "$#{name}"
+ end
end
end
end
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index 8114e838ede..659b8f10e0a 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -98,7 +98,7 @@ module QA
#
# @return [void]
def setup_logger!
- Knapsack.logger = QA::Runtime::Logger.logger
+ Knapsack.logger = logger
end
# Set knapsack environment variables
@@ -112,9 +112,9 @@ module QA
# Logger instance
#
- # @return [Logger]
+ # @return [ActiveSupport::Logger]
def logger
- @logger ||= Knapsack.logger
+ QA::Runtime::Logger.logger
end
# GCS client
diff --git a/qa/qa/support/loglinking.rb b/qa/qa/support/loglinking.rb
index caf381912d3..ceddd35da17 100644
--- a/qa/qa/support/loglinking.rb
+++ b/qa/qa/support/loglinking.rb
@@ -8,18 +8,18 @@ module QA
PRODUCTION_ADDRESS = 'https://gitlab.com'
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
SENTRY_ENVIRONMENTS = {
- staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
+ staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
staging_canary: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
- staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
- pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
- canary: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
- production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny'
+ staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
+ pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
+ canary: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
+ production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny'
}.freeze
KIBANA_ENVIRONMENTS = {
- staging: 'https://nonprod-log.gitlab.net/',
+ staging: 'https://nonprod-log.gitlab.net/',
staging_canary: 'https://nonprod-log.gitlab.net/',
- canary: 'https://log.gprd.gitlab.net/',
- production: 'https://log.gprd.gitlab.net/'
+ canary: 'https://log.gprd.gitlab.net/',
+ production: 'https://log.gprd.gitlab.net/'
}.freeze
def self.failure_metadata(correlation_id)
diff --git a/qa/qa/support/parallel_pipeline_jobs.rb b/qa/qa/support/parallel_pipeline_jobs.rb
new file mode 100644
index 00000000000..a551bf9978b
--- /dev/null
+++ b/qa/qa/support/parallel_pipeline_jobs.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ # Helper utility to fetch parallel job names in a given pipelines stage
+ #
+ class ParallelPipelineJobs
+ include API
+
+ PARALLEL_JOB_NAME_PATTERN = %r{^\S+ \d+/\d+$}.freeze
+
+ def initialize(stage_name:, project_id:, pipeline_id:, access_token:)
+ @stage_name = stage_name
+ @access_token = access_token
+ @project_id = project_id || raise("project_id must be provided")
+ @pipeline_id = pipeline_id || raise("pipeline_id must be provided")
+ end
+
+ # Fetch parallel job names in given stage
+ #
+ # Default to arguments available on CI
+ #
+ # @param [String] stage_name
+ # @param [Integer] project_id
+ # @param [Integer] pipeline_id
+ # @param [String] access_token
+ # @return [Array]
+ def self.fetch(
+ stage_name:,
+ access_token:,
+ project_id: ENV["CI_PROJECT_ID"],
+ pipeline_id: ENV["CI_PIPELINE_ID"]
+ )
+ new(
+ stage_name: stage_name,
+ project_id: project_id,
+ pipeline_id: pipeline_id,
+ access_token: access_token
+ ).parallel_jobs
+ end
+
+ # Parallel job list
+ #
+ # @return [Array<String>]
+ def parallel_jobs
+ api_get("projects/#{project_id}/pipelines/#{pipeline_id}/jobs?per_page=100")
+ .select { |job| job[:stage] == stage_name && job[:name].match?(PARALLEL_JOB_NAME_PATTERN) }
+ .map { |job| job[:name].gsub(%r{ \d+/\d+}, "") }
+ .uniq
+ end
+
+ private
+
+ attr_reader :stage_name, :access_token, :project_id, :pipeline_id
+
+ # Api get request
+ #
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_get(path)
+ response = get("#{api_url}/#{path}", { headers: { "PRIVATE-TOKEN" => access_token } })
+ raise "Failed to fetch pipeline jobs: '#{response.body}'" unless response.code == API::HTTP_STATUS_OK
+
+ parse_body(response)
+ end
+
+ # Gitlab api url
+ #
+ # @return [String]
+ def api_url
+ @api_url ||= ENV['CI_API_V4_URL'] || "https://gitlab.com/api/v4"
+ end
+ end
+ end
+end
diff --git a/qa/qa/tools/delete_projects.rb b/qa/qa/tools/delete_projects.rb
index 96ea5f8de7e..da46606f286 100644
--- a/qa/qa/tools/delete_projects.rb
+++ b/qa/qa/tools/delete_projects.rb
@@ -9,6 +9,7 @@ module QA
module Tools
class DeleteProjects
include Support::API
+ include Lib::Project
def initialize
raise ArgumentError, "Please provide GITLAB_ADDRESS environment variable" unless ENV['GITLAB_ADDRESS']
@@ -30,25 +31,12 @@ module QA
project_ids = fetch_project_ids(group_id, total_project_pages)
$stdout.puts "Number of projects to be deleted: #{project_ids.length}"
- delete_projects(project_ids) unless project_ids.empty?
+ delete_projects(project_ids, @api_client) unless project_ids.empty?
$stdout.puts "\nDone"
end
private
- def delete_projects(project_ids)
- $stdout.puts "Deleting #{project_ids.length} projects..."
- project_ids.each do |project_id|
- request_url = Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url
- path = parse_body(get(request_url))[:path_with_namespace]
- $stdout.puts "\nDeleting project #{path}..."
-
- delete_response = delete(request_url)
- dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m"
- print dot_or_f
- end
- end
-
def fetch_group_id
group_name = ENV['TOP_LEVEL_GROUP_NAME'] || "gitlab-qa-sandbox-group-#{Time.now.wday + 1}"
group_search_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_name}").url
diff --git a/qa/qa/tools/delete_user_projects.rb b/qa/qa/tools/delete_user_projects.rb
new file mode 100644
index 00000000000..9c031e352b4
--- /dev/null
+++ b/qa/qa/tools/delete_user_projects.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# This script deletes all projects owned by a given USER_ID in their personal namespace
+# Required environment variables: USER_ID, GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
+# Run `rake delete_user_projects`
+
+module QA
+ module Tools
+ class DeleteUserProjects
+ include Support::API
+ include Lib::Project
+
+ def initialize(delete_before: (Date.today - 1).to_s, dry_run: false)
+ unless ENV['GITLAB_ADDRESS']
+ raise ArgumentError, "Please provide GITLAB_ADDRESS environment variable"
+ end
+
+ unless ENV['GITLAB_QA_ACCESS_TOKEN']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN environment variable"
+ end
+
+ unless ENV['USER_ID']
+ raise ArgumentError, "Please provide USER_ID environment variable"
+ end
+
+ @delete_before = Date.parse(delete_before)
+ @dry_run = dry_run
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'],
+ personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
+ end
+
+ def run
+ $stdout.puts 'Running...'
+
+ projects_head_response = head Runtime::API::Request.new(@api_client, "/users/#{ENV['USER_ID']}/projects",
+ per_page: "100").url
+ total_project_pages = projects_head_response.headers[:x_total_pages]
+
+ $stdout.puts "Total project pages: #{total_project_pages}"
+
+ project_ids = fetch_project_ids(total_project_pages)
+
+ delete_projects(project_ids, @api_client, @dry_run) unless project_ids.empty?
+ $stdout.puts "\nDone"
+ end
+
+ private
+
+ def fetch_project_ids(total_project_pages)
+ projects_ids = []
+
+ total_project_pages.to_i.times do |page_no|
+ projects_response = get Runtime::API::Request.new(@api_client, "/users/#{ENV['USER_ID']}/projects",
+ page: (page_no + 1).to_s, per_page: "100").url
+ projects_ids.concat(JSON.parse(projects_response.body)
+ .select { |project| Date.parse(project["created_at"]) < @delete_before }
+ .map { |project| project["id"] })
+ end
+
+ projects_ids.uniq
+ end
+ end
+ end
+end
diff --git a/qa/qa/tools/lib/project.rb b/qa/qa/tools/lib/project.rb
new file mode 100644
index 00000000000..52e5e5c63a4
--- /dev/null
+++ b/qa/qa/tools/lib/project.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ module Tools
+ module Lib
+ module Project
+ def delete_projects(project_ids, api_client, dry_run = false)
+ if dry_run
+ $stdout.puts "Following #{project_ids.length} projects would be deleted:"
+ else
+ $stdout.puts "Deleting #{project_ids.length} projects..."
+ end
+
+ project_ids.each do |project_id|
+ request_url = Runtime::API::Request.new(api_client, "/projects/#{project_id}").url
+ parsed_body = parse_body(get(request_url))
+ path = parsed_body[:path_with_namespace]
+ created_at = parsed_body[:created_at]
+
+ if dry_run
+ $stdout.puts "#{path} - created at: #{created_at}"
+ else
+ $stdout.puts "\nDeleting project #{path} - created at: #{created_at}"
+ delete_response = delete(request_url)
+ dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m"
+ print dot_or_f
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/jenkins/README.md b/qa/qa/vendor/jenkins/README.md
new file mode 100644
index 00000000000..98304bd7cb8
--- /dev/null
+++ b/qa/qa/vendor/jenkins/README.md
@@ -0,0 +1,61 @@
+## jenkins_client
+
+For use with [this jenkins docker image](https://gitlab.com/gitlab-org/quality/third-party-docker-images/jenkins)
+
+### usage
+
+instantiate client
+
+```ruby
+client = Jenkins::Client.new(
+ '127.0.0.1',
+ user: ENV['JENKINS_ADMIN_USER'],
+ password: ENV['JENKINS_ADMIN_PASS'],
+)
+```
+
+configure gitlab plugin and create a job
+
+```ruby
+connection_name = 'gitlab-connection'
+
+client.configure_gitlab_plugin(
+ ENV['GITLAB_URL'],
+ connection_name: connection_name,
+ access_token: ENV['GITLAB_ACCESS_TOKEN'],
+ read_timeout: 20,
+ connection_timeout: 10
+)
+
+job = client.create_job 'Job Name' do |job|
+ job.gitlab_connection = connection_name
+ job.description = 'Job Description'
+ job.repo_url = 'https://location-of-project.git'
+ job.shell_command = 'sleep 20'
+end
+```
+
+view info about the job
+
+```ruby
+while job.running?
+ puts "Number of active builds: #{job.active_runs}"
+end
+
+puts "Last build status #{job.status}"
+puts "Last build log #{job.log}"
+```
+
+### developer notes
+
+This client makes extensive use of the [/script api](https://docs.cloudbees.com/docs/cloudbees-ci-kb/latest/client-and-managed-masters/execute-groovy-with-a-rest-call).
+
+Groovy is a dynamic language hosted on the Java platform. Please refer to https://learnxinyminutes.com/docs/groovy/ for basic syntax.
+
+The scripts may reference the code api of jenkins and the gitlab jenkins plugin. Here are some articles and source files to reference when debugging.
+
+* [Setting Jenkins Credentials](https://nickcharlton.net/posts/setting-jenkins-credentials-with-groovy.html)
+* [GitLabConnectionConfig](https://github.com/jenkinsci/gitlab-plugin/blob/master/src/main/java/com/dabsquared/gitlabjenkins/connection/GitLabConnectionConfig.java) and [GitLabConnection](https://github.com/jenkinsci/gitlab-plugin/blob/master/src/main/java/com/dabsquared/gitlabjenkins/connection/GitLabConnection.java)
+* [Jenkins.instance.getProjects](https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/model/Jenkins.java#L1878)
+* [Job.getBuilds](https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Job.java#L734)
+* [Run.getResult](https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Run.java#L491)
diff --git a/qa/qa/vendor/jenkins/client.rb b/qa/qa/vendor/jenkins/client.rb
new file mode 100644
index 00000000000..d7666255010
--- /dev/null
+++ b/qa/qa/vendor/jenkins/client.rb
@@ -0,0 +1,298 @@
+# frozen_string_literal: true
+
+require 'base64'
+require 'cgi'
+require 'fileutils'
+require 'json'
+require 'nokogiri'
+require 'rest-client'
+require 'securerandom'
+
+require_relative './helpers'
+require_relative './job'
+
+module QA
+ module Vendor
+ module Jenkins
+ NetworkError = Class.new(StandardError)
+ NotParseableError = Class.new(StandardError)
+ class Client
+ include Helpers
+
+ attr_accessor :cookies
+
+ DEFAULT_SERVER_PORT = 8080
+
+ # @param host [String] the ip or hostname of the jenkins server
+ # @param user [String] the Jenkins admin user
+ # @param password [String] the Jenkins admin password
+ # @param port [Integer] the port that Jenkins is serving on
+ def initialize(host, user:, password:, port: nil)
+ @host = host
+ @user = user
+ @password = password
+ @port = port
+ @cookies = {}
+ end
+
+ def ready?
+ !!try_parse(RestClient.get(crumb_path, auth_headers).body)
+ end
+
+ # Creates a new job in Jenkins
+ #
+ # @param name [String] the name of the job
+ # @yieldparam job [Jenkins::Job] the job to be configured
+ # @return [Jenkins::Job] the created job in Jenkins
+ def create_job(name)
+ job = Job.new(name, self)
+ yield job if block_given?
+ job.create
+ job
+ end
+
+ # Is a given job running?
+ #
+ # @param name [String] the name of the job
+ # @return [Boolean] is the job running?
+ def job_running?(name)
+ res = execute <<~GROOVY
+ project = Jenkins.instance.getProjects().find{p -> p.getName().equals('#{name}')}
+ build = project.getBuilds().find{b -> b.getExecutor()}
+ return build ? build.getExecutor().isActive() : false
+ GROOVY
+ JSON.parse parse_result(res)
+ end
+
+ # Number of builds currently executing for a given job
+ #
+ # @param name [String] the name of the job
+ # @return [Integer] the number of builds currently running
+ def number_of_jobs_running(name)
+ res = execute <<~GROOVY
+ project = Jenkins.instance.getProjects().find{p -> p.getName().equals('#{name}')}
+ builds = project.getBuilds().findAll{b -> b.getExecutor()}
+ return builds.size
+ GROOVY
+ JSON.parse parse_result(res)&.to_i
+ end
+
+ # Latest build status for a job
+ #
+ # @param name [String] the name of the job
+ # @return [Symbol] the latest build status eg, (:success, :failure, etc)
+ def last_build_status(name)
+ res = execute <<~GROOVY
+ project = Jenkins.instance.getProjects().find{p -> p.getName().equals('#{name}')}
+ build = project.getBuilds()[-1]
+ return build.getResult()
+ GROOVY
+ parse_result(res)&.downcase&.to_sym
+ end
+
+ # Latest build id for a job
+ # Can be used to reference in other queries
+ #
+ # @param job_name [String] the name of the job
+ # @return [Integer] the latest build id
+ def last_build_id(job_name)
+ res = execute <<~GROOVY
+ project = Jenkins.instance.getProjects().find{p -> p.getName().equals('#{job_name}')}
+ build = project.getBuilds()[-1]
+ return build.getId()
+ GROOVY
+ parse_result(res)&.to_i
+ end
+
+ # Latest build log for a job
+ #
+ # @param job_name [String] the name of the job
+ # @param start [Integer] the log offset to return
+ # @return [String] the latest Jenkins log/output for this job
+ def last_build_log(job_name, start = 0)
+ get(
+ path: "/job/#{job_name}/#{last_build_id(job_name)}/logText/progressiveText",
+ params: { start: start }
+ ).body
+ end
+
+ # Triggers a build for a given job
+ #
+ # @param name [String] the name of the job to trigger a build for
+ # @param [Hash] params the query parameters as a hash for the build endpoint
+ def build(name, params: {})
+ post(params, path: "/job/#{name}/build")
+ end
+
+ # Executes a Groovy script against the Jenkins instance
+ #
+ # @param script [String] the Groovy script to execute
+ def execute(script)
+ post("script=#{script}", path: '/scriptText')
+ end
+
+ # Sends XML to a given Jenkins endpoint
+ # This might be useful for filling in gaps in this lib
+ #
+ # @param xml [String] the xml to post
+ # @param params [Hash] the query parameters as a hash
+ # @param path [String] the path to post to ex: /job/<name>/build
+ # @return [Typhoeus::Response]
+ def post_xml(xml, params: {}, path: '')
+ post(xml, params: params, path: path, headers: { 'Content-Type' => 'text/xml' })
+ end
+
+ # Posts data to Jenkins
+ # This might be useful for filling in gaps in this lib
+ #
+ # @param data [String | Hash] the xml to post
+ # @param params [Hash] the query parameters as a hash
+ # @param path [String] the path to post to ex: /job/<name>/build
+ # @param headers [Hash] additional headers to send
+ # @return [Typhoeus::Response]
+ def post(data, params: {}, path: '', headers: {})
+ get_crumb
+ RestClient.post(
+ "#{api_path}#{path}?#{params_to_s(params)}",
+ data,
+ headers.merge(full_headers)
+ )
+ end
+
+ # Gets from a Jenkins endpoint
+ # This might be useful for filling in gaps in this lib
+ #
+ # @param path [String] the path to get from ex: /job/<name>/builds/<build_id>/logText/progressiveText
+ # @param params [Hash] the query parameters as a hash
+ # @return [Typhoeus::Response]
+ def get(path: '', params: {})
+ get_crumb
+ RestClient.get(
+ "#{api_path}#{path}?#{params_to_s(params)}",
+ full_headers
+ )
+ end
+
+ # configures the Jenkins GitLab plugin
+ #
+ # @param url [String] the url for the GitLab instance
+ # @param access_token [String] an access token for the GitLab instance
+ # @param secret_id [String] an secret id used for the Jenkins GitLab credentials
+ # @param hargs [Hash] extra keyword arguments to provide
+ # @option hargs [String] :connection_name the name to use for the gitlab connection
+ # @option hargs [Integer] :read_timeout the read timeout for GitLab Jenkins
+ # @option hargs [Integer] :connection_timeout the connection timeout for GitLab Jenkins
+ # @option hargs [Boolean] :ignore_ssl_errors whether GitLab Jenkins should ignore SSL errors
+ # @return [String] the execute response from Jenkins
+ def configure_gitlab_plugin(url, access_token:, secret_id: SecureRandom.hex(4), **hargs)
+ configure_secret(access_token, secret_id)
+ configure_gitlab(url, secret_id, **hargs)
+ end
+
+ private
+
+ def parse_result(res)
+ check_network_error(res)
+
+ res.body.scan(/Result: (.*)/)&.dig(0, 0)
+ end
+
+ def configure_gitlab(
+ url,
+ secret_id,
+ connection_name: 'default',
+ read_timeout: 10,
+ connection_timeout: 10,
+ ignore_ssl_errors: true
+ )
+ res = execute <<~GROOVY
+ import com.dabsquared.gitlabjenkins.connection.*;
+ conn = new GitLabConnection(
+ "#{connection_name}",
+ "#{url}",
+ "#{secret_id}",
+ #{ignore_ssl_errors},
+ #{connection_timeout},
+ #{read_timeout}
+ );
+
+ config = GitLabConnectionConfig.get();
+ config.setConnections([conn]);
+ GROOVY
+ res.body
+ end
+
+ def configure_secret(access_token, credential_id)
+ execute <<~GROOVY
+ import jenkins.model.Jenkins;
+ import com.cloudbees.plugins.credentials.domains.Domain;
+ import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
+ import com.cloudbees.plugins.credentials.CredentialsScope;
+ import hudson.util.Secret;
+
+ instance = Jenkins.instance;
+ domain = Domain.global();
+ store = instance.getExtensionList("com.cloudbees.plugins.credentials.SystemCredentialsProvider")[0].getStore();
+
+ secretText = new StringCredentialsImpl(
+ CredentialsScope.GLOBAL,
+ "#{credential_id}",
+ "GitLab API Token",
+ Secret.fromString("#{access_token}")
+ );
+
+ store.addCredentials(domain, secretText);
+ GROOVY
+ end
+
+ def get_crumb
+ return if @crumb
+
+ response = RestClient.get(crumb_path, auth_headers)
+ response_body = handle_json_response(response)
+ @crumb = response_body['crumb']
+ end
+
+ def params_to_s(params)
+ params.each_with_object([]) do |(k, v), memo|
+ memo << "#{k}=#{v}"
+ end.join('&')
+ end
+
+ def full_headers
+ crumb_headers
+ .merge(auth_headers)
+ .merge(cookie_headers)
+ end
+
+ def crumb_headers
+ { 'Jenkins-Crumb' => @crumb }
+ end
+
+ def auth_headers
+ { 'Authorization' => "Basic #{userpwd}" }
+ end
+
+ def cookie_headers
+ { cookies: @cookies }
+ end
+
+ def userpwd
+ Base64.encode64("#{@user}:#{@password}")
+ end
+
+ def api_path
+ "http://#{@host}:#{port}"
+ end
+
+ def crumb_path
+ "#{api_path}/crumbIssuer/api/json"
+ end
+
+ def port
+ @port || DEFAULT_SERVER_PORT
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/jenkins/helpers.rb b/qa/qa/vendor/jenkins/helpers.rb
new file mode 100644
index 00000000000..38175d5687a
--- /dev/null
+++ b/qa/qa/vendor/jenkins/helpers.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Jenkins
+ module Helpers
+ private
+
+ def try_parse(string)
+ JSON.parse(string)
+ rescue StandardError => _e
+ nil
+ end
+
+ def check_network_error(response)
+ raise NetworkError, "#{response.code} - #{response.body}" if response.code >= 400
+ end
+
+ def handle_json_response(response)
+ check_network_error(response)
+ set_cookies(response)
+
+ unless (data = try_parse(response.body))
+ raise NotParseableError, "Code: #{response.code}\nBody: #{response.body}"
+ end
+
+ data
+ end
+
+ def set_cookies(response)
+ self.cookies = response.cookies
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/jenkins/job.rb b/qa/qa/vendor/jenkins/job.rb
new file mode 100644
index 00000000000..46048960fae
--- /dev/null
+++ b/qa/qa/vendor/jenkins/job.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Jenkins
+ class Job
+ include Helpers
+
+ REQUIRED_BUILD_FIELDS = %i[name description shell_command].freeze
+
+ attr_accessor(
+ :name,
+ :description,
+ :keep_deps,
+ :can_roam,
+ :disabled,
+ :repo_url,
+ :gitlab_connection,
+ :shell_command
+ )
+
+ # Prefer Jenkins::Client#jobs and Jenkins::Client.create_job over this constructor
+ #
+ # @param name [String] the name of the job
+ # @param client [Jenkins::Client] the jenkins client
+ def initialize(name, client)
+ @name = name
+ @client = client
+ end
+
+ # Saves the Job in Jenkins
+ def create
+ validate_required_fields!
+
+ response = @client.post_xml(build, path: '/createItem', params: { name: name })
+
+ check_network_error(response)
+ response.body
+ end
+
+ # Triggers a build for the job
+ def run
+ @client.build(@name)
+ end
+
+ # Returns the jobs last build status
+ def status
+ @client.last_build_status(@name)
+ end
+
+ # Returns the jobs last log
+ #
+ # @param start [Integer] the log offset to query
+ def log(start: 0)
+ @client.last_build_log(@name, start)
+ end
+
+ # Returns whether the job is running
+ #
+ # @return [Boolean]
+ def running?
+ @client.job_running?(@name)
+ end
+
+ # Returns the count of active builds
+ #
+ # @return [Integer]
+ def active_runs
+ @client.number_of_jobs_running(@name)
+ end
+
+ private
+
+ def validate_required_fields!
+ error = REQUIRED_BUILD_FIELDS.each_with_object("") do |field, memo|
+ memo << "#{field} is required\n" unless send(field)
+ end
+ raise ArgumentError, error unless error.empty?
+ end
+
+ def build
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ xml.project do
+ xml.actions
+ xml.description description
+ xml.keepDependencies false
+ xml.properties do |props|
+ build_gitlab_connection(props)
+ end
+ xml.canRoam true
+ xml.disabled false
+ xml.blockBuildWhenDownstreamBuilding false
+ xml.blockBuildWhenUpstreamBuilding false
+ xml.triggers do |triggers|
+ build_gitlab_triggers(triggers)
+ end
+ xml.concurrentBuild false
+ xml.builders do
+ xml.send('hudson.tasks.Shell') do
+ xml.command shell_command
+ xml.configuredLocalRules
+ end
+ end
+ xml.publishers do |publishers|
+ build_gitlab_publishers(publishers)
+ end
+ xml.buildWrappers
+ build_scm(xml)
+ end
+ end
+ builder.to_xml
+ end
+
+ def build_scm(xml)
+ if repo_url
+ xml.scm(class: 'hudson.plugins.git.GitSCM') do
+ xml.userRemoteConfigs do
+ xml.send('hudson.plugins.git.UserRemoteConfig') do
+ xml.url repo_url
+ end
+ end
+ xml.branches do
+ xml.send('hudson.plugins.git.BranchSpec') do
+ xml.name
+ end
+ end
+ xml.configVersion 2
+ xml.doGenerateSubmoduleConfiguration false
+ xml.gitTool 'Default'
+ end
+ end
+ end
+
+ def build_gitlab_connection(xml)
+ if gitlab_connection
+ xml.send('com.dabsquared.gitlabjenkins.connection.GitLabConnectionProperty') do
+ xml.gitLabConnection gitlab_connection
+ end
+ end
+ end
+
+ def build_gitlab_triggers(xml)
+ if gitlab_connection
+ xml.send('com.dabsquared.gitlabjenkins.GitLabPushTrigger') do
+ xml.spec
+ xml.triggerOnPush true
+ xml.triggerOnMergeRequest true
+ xml.includeBranchesSpec 'main,master'
+ xml.branchFilterType 'NameBasedFilter'
+ xml.ciSkip true
+ end
+ end
+ end
+
+ def build_gitlab_publishers(xml)
+ if gitlab_connection
+ xml.send('com.dabsquared.gitlabjenkins.publisher.GitLabCommitStatusPublisher') do
+ xml.name 'jenkins'
+ xml.markUnstableAsSuccess false
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/jenkins/page/base.rb b/qa/qa/vendor/jenkins/page/base.rb
deleted file mode 100644
index 8dfbe7570f8..00000000000
--- a/qa/qa/vendor/jenkins/page/base.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class Base
- include Capybara::DSL
- include Scenario::Actable
-
- attr_reader :path
-
- class << self
- attr_accessor :host
- end
-
- def visit!
- page.visit URI.join(Base.host, path).to_s
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/configure.rb b/qa/qa/vendor/jenkins/page/configure.rb
deleted file mode 100644
index da59060152d..00000000000
--- a/qa/qa/vendor/jenkins/page/configure.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class Configure < Page::Base
- def initialize
- @path = 'configure'
- end
-
- def visit_and_setup_gitlab_connection(gitlab_host, token_description)
- visit!
- fill_in '_.name', with: 'GitLab'
- find('.setting-name', text: "Gitlab host URL").find(:xpath, "..").find('input').set gitlab_host
-
- dropdown_element = find('.setting-name', text: "Credentials").find(:xpath, "..").find('select')
-
- QA::Support::Retrier.retry_until(raise_on_failure: true) do
- dropdown_element.select "GitLab API token (#{token_description})"
- dropdown_element.value != ''
- end
-
- yield if block_given?
-
- click_save
- end
-
- def click_test_connection
- click_on 'Test Connection'
- end
-
- def has_success?
- has_css?('div.ok', text: "Success")
- end
-
- private
-
- def click_save
- click_on 'Save'
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/configure_job.rb b/qa/qa/vendor/jenkins/page/configure_job.rb
deleted file mode 100644
index 65719795720..00000000000
--- a/qa/qa/vendor/jenkins/page/configure_job.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class ConfigureJob < Page::Base
- attr_accessor :job_name
-
- def path
- "/job/#{@job_name}/configure"
- end
-
- def configure(scm_url:)
- set_git_source_code_management_url(scm_url)
- set_git_branches_to_build("*/#{Runtime::Env.default_branch}")
- click_build_when_change_is_pushed_to_gitlab
- set_publish_status_to_gitlab
-
- Support::Retrier.retry_until(sleep_interval: 0.5) do
- click_save
- wait_for_configuration_to_save
- end
- end
-
- private
-
- def set_git_source_code_management_url(repository_url)
- select_git_source_code_management
- set_repository_url(repository_url)
- end
-
- def set_git_branches_to_build(branches)
- find('.setting-name', text: "Branch Specifier (blank for 'any')").find(:xpath, "..").find('input').set branches
- end
-
- def click_build_when_change_is_pushed_to_gitlab
- find('label', text: 'Build when a change is pushed to GitLab').find(:xpath, "..").find('input').click
- end
-
- def set_publish_status_to_gitlab
- click_add_post_build_action
- select_publish_build_status_to_gitlab
- end
-
- def click_save
- click_on 'Save'
- end
-
- def select_git_source_code_management
- find('#radio-block-1').click
- end
-
- def set_repository_url(repository_url)
- find('.setting-name', text: "Repository URL").find(:xpath, "..").find('input').set repository_url
- end
-
- def click_add_post_build_action
- click_on "Add post-build action"
- end
-
- def select_publish_build_status_to_gitlab
- click_link "Publish build status to GitLab"
- end
-
- def wait_for_configuration_to_save
- QA::Support::Waiter.wait_until(max_duration: 10, raise_on_failure: false) do
- !page.current_url.include?(path)
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/last_job_console.rb b/qa/qa/vendor/jenkins/page/last_job_console.rb
deleted file mode 100644
index 9fcbb8ab956..00000000000
--- a/qa/qa/vendor/jenkins/page/last_job_console.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class LastJobConsole < Page::Base
- attr_accessor :job_name
-
- CONSOLE_OUTPUT_SELECTOR = '.console-output'
-
- def path
- "/job/#{@job_name}/lastBuild/console"
- end
-
- def has_successful_build?
- # Retry on errors such as:
- # Selenium::WebDriver::Error::JavascriptError:
- # javascript error: this.each is not a function
- Support::Retrier.retry_on_exception(reload_page: page, sleep_interval: 1) do
- has_console_output? && console_output.include?('Finished: SUCCESS')
- end
- end
-
- def no_failed_status_update?
- !console_output.include?('Failed to update Gitlab commit status')
- end
-
- private
-
- def has_console_output?
- page.has_selector?(CONSOLE_OUTPUT_SELECTOR, wait: 1)
- end
-
- def console_output
- page.find(CONSOLE_OUTPUT_SELECTOR).text
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/login.rb b/qa/qa/vendor/jenkins/page/login.rb
deleted file mode 100644
index b18c02b5a44..00000000000
--- a/qa/qa/vendor/jenkins/page/login.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class Login < Page::Base
- def initialize
- @path = 'login'
- end
-
- def visit!
- super
-
- QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, raise_on_failure: true) do
- page.has_text? 'Welcome to Jenkins!'
- end
- end
-
- def login
- fill_in 'j_username', with: 'admin'
- fill_in 'j_password', with: 'password'
- click_on 'Sign in'
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/new_credentials.rb b/qa/qa/vendor/jenkins/page/new_credentials.rb
deleted file mode 100644
index b0d13973090..00000000000
--- a/qa/qa/vendor/jenkins/page/new_credentials.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class NewCredentials < Page::Base
- def initialize
- @path = 'credentials/store/system/domain/_/newCredentials'
- end
-
- def visit_and_set_gitlab_api_token(api_token, description)
- visit!
- wait_for_page_to_load
- select_gitlab_api_token
- set_api_token(api_token)
- set_description(description)
- click_ok
- end
-
- private
-
- def select_gitlab_api_token
- find('.setting-name', text: "Kind").find(:xpath, "..").find('select').select "GitLab API token"
- end
-
- def set_api_token(api_token)
- fill_in '_.apiToken', with: api_token
- end
-
- def set_description(description)
- fill_in '_.description', with: description
- end
-
- def click_ok
- click_on 'OK'
- end
-
- def wait_for_page_to_load
- QA::Support::Waiter.wait_until(sleep_interval: 1.0) do
- page.has_css?('.setting-name', text: "Description")
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/jenkins/page/new_job.rb b/qa/qa/vendor/jenkins/page/new_job.rb
deleted file mode 100644
index 11fa4ca8a53..00000000000
--- a/qa/qa/vendor/jenkins/page/new_job.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'capybara/dsl'
-
-module QA
- module Vendor
- module Jenkins
- module Page
- class NewJob < Page::Base
- def initialize
- @path = 'newJob'
- end
-
- def visit_and_create_new_job_with_name(new_job_name)
- visit!
- set_new_job_name(new_job_name)
- click_free_style_project
- click_ok
- end
-
- private
-
- def set_new_job_name(new_job_name)
- fill_in 'name', with: new_job_name
- end
-
- def click_free_style_project
- find('.hudson_model_FreeStyleProject').click
- end
-
- def click_ok
- click_on 'OK'
- end
- end
- end
- end
- end
-end
diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb
index 2a68dd23219..ecc8e0e0f2c 100644
--- a/qa/spec/scenario/test/sanity/selectors_spec.rb
+++ b/qa/spec/scenario/test/sanity/selectors_spec.rb
@@ -5,20 +5,26 @@ RSpec.describe QA::Scenario::Test::Sanity::Selectors do
before do
stub_const('QA::Page::Validator', validator)
+
+ allow(QA::Runtime::Logger).to receive(:warn)
+ allow(QA::Runtime::Logger).to receive(:info)
end
context 'when there are errors detected' do
+ let(:error) { 'some error' }
+
before do
- allow(validator).to receive(:errors).and_return(['some error'])
+ allow(validator).to receive(:errors).and_return([error])
end
- it 'outputs information about errors' do
- expect { described_class.perform }
- .to output(/some error/).to_stderr
+ it 'outputs information about errors', :aggregate_failures do
+ described_class.perform
+
+ expect(QA::Runtime::Logger).to have_received(:warn)
+ .with(/GitLab QA sanity selectors validation test detected problems/)
- expect { described_class.perform }
- .to output(/electors validation test detected problems/)
- .to_stderr
+ expect(QA::Runtime::Logger).to have_received(:warn)
+ .with(/#{error}/)
end
end
diff --git a/qa/spec/service/docker_run/gitlab_runner_spec.rb b/qa/spec/service/docker_run/gitlab_runner_spec.rb
index d9f201cf67e..dd4aaf8f0a1 100644
--- a/qa/spec/service/docker_run/gitlab_runner_spec.rb
+++ b/qa/spec/service/docker_run/gitlab_runner_spec.rb
@@ -33,17 +33,21 @@ module QA
end
it 'runs non-interactively' do
- expect(subject).to have_received(:shell).with(/ --non-interactive /)
+ expect(subject).to have_received_masked_shell_command(/ --non-interactive /)
end
it 'sets pertinent information' do
- expect(subject).to have_received(:shell).with(/--name #{runner_name} /)
- expect(subject).to have_received(:shell).with(/--url #{subject.address} /)
- expect(subject).to have_received(:shell).with(/--registration-token #{subject.token} /)
+ expect(subject).to have_received_masked_shell_command(/--name #{runner_name} /)
+ expect(subject).to have_received_masked_shell_command(/--url #{subject.address} /)
+ expect(subject).to have_received_masked_shell_command(/--registration-token \S+/)
+ end
+
+ it 'masks the registration token' do
+ expect(subject).to have_received(:shell).with(/#{subject.token}/, mask_secrets: [subject.token])
end
it 'runs untagged' do
- expect(subject).to have_received(:shell).with(/--run-untagged=true /)
+ expect(subject).to have_received_masked_shell_command(/--run-untagged=true /)
end
it 'has no tags' do
@@ -51,11 +55,11 @@ module QA
end
it 'runs daemonized' do
- expect(subject).to have_received(:shell).with(/ -d /)
+ expect(subject).to have_received_masked_shell_command(/ -d /)
end
it 'cleans itself up' do
- expect(subject).to have_received(:shell).with(/ --rm /)
+ expect(subject).to have_received_masked_shell_command(/ --rm /)
end
end
@@ -65,11 +69,11 @@ module QA
end
it 'passes --run-untagged=true' do
- expect(subject).to have_received(:shell).with(/--run-untagged=true /)
+ expect(subject).to have_received_masked_shell_command(/--run-untagged=true /)
end
it 'does not pass tag list' do
- expect(subject).not_to have_received(:shell).with(/--tag-list/)
+ expect(subject).not_to have_received_masked_shell_command(/--tag-list/)
end
end
@@ -82,11 +86,11 @@ module QA
end
it 'does not pass --run-untagged' do
- expect(subject).not_to have_received(:shell).with(/--run-untagged=true/)
+ expect(subject).not_to have_received_masked_shell_command(/--run-untagged=true/)
end
it 'passes the tags with comma-separation' do
- expect(subject).to have_received(:shell).with(/--tag-list #{tags.join(',')} /)
+ expect(subject).to have_received_masked_shell_command(/--tag-list #{tags.join(',')} /)
end
end
@@ -116,7 +120,7 @@ module QA
it 'defaults to the shell executor' do
register
- expect(subject).to have_received(:shell).with(/--executor shell /)
+ expect(subject).to have_received_masked_shell_command(/--executor shell /)
end
context 'docker' do
@@ -127,31 +131,31 @@ module QA
end
it 'specifies the docker executor' do
- expect(subject).to have_received(:shell).with(/--executor docker /)
+ expect(subject).to have_received_masked_shell_command(/--executor docker /)
end
it 'mounts the docker socket to the host runner' do
- expect(subject).to have_received(:shell).with(%r{-v /var/run/docker.sock:/var/run/docker.sock })
+ expect(subject).to have_received_masked_shell_command(%r{-v /var/run/docker.sock:/var/run/docker.sock })
end
it 'runs in privileged mode' do
- expect(subject).to have_received(:shell).with(/--privileged /)
+ expect(subject).to have_received_masked_shell_command(/--privileged /)
end
it 'has a default image' do
- expect(subject).to have_received(:shell).with(/--docker-image \b.+\b /)
+ expect(subject).to have_received_masked_shell_command(/--docker-image \b.+\b /)
end
it 'does not verify TLS' do
- expect(subject).to have_received(:shell).with(/--docker-tlsverify=false /)
+ expect(subject).to have_received_masked_shell_command(/--docker-tlsverify=false /)
end
it 'passes privileged mode' do
- expect(subject).to have_received(:shell).with(/--docker-privileged=true /)
+ expect(subject).to have_received_masked_shell_command(/--docker-privileged=true /)
end
it 'passes the host network' do
- expect(subject).to have_received(:shell).with(/--docker-network-mode=#{subject.network}/)
+ expect(subject).to have_received_masked_shell_command(/--docker-network-mode=#{subject.network}/)
end
end
end
@@ -170,5 +174,15 @@ module QA
expect(subject.run_untagged).to be(false)
end
end
+
+ RSpec::Matchers.define "have_received_masked_shell_command" do |cmd|
+ match do |actual|
+ expect(actual).to have_received(:shell).with(cmd, mask_secrets: anything)
+ end
+
+ match_when_negated do |actual|
+ expect(actual).not_to have_received(:shell).with(cmd, mask_secrets: anything)
+ end
+ end
end
end
diff --git a/qa/spec/support/loglinking_spec.rb b/qa/spec/support/loglinking_spec.rb
index e02ae45ee93..d1cc76bccc4 100644
--- a/qa/spec/support/loglinking_spec.rb
+++ b/qa/spec/support/loglinking_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe QA::Support::Loglinking do
it 'if correlation_id is empty' do
expect(QA::Support::Loglinking.failure_metadata('')).to eq(nil)
end
+
it 'if correlation_id is nil' do
expect(QA::Support::Loglinking.failure_metadata(nil)).to eq(nil)
end
@@ -37,14 +38,14 @@ RSpec.describe QA::Support::Loglinking do
describe '.sentry_url' do
let(:url_hash) do
{
- :staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
- :staging_canary => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
- :staging_ref => 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
- :pre => 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
- :canary => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
- :production => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny',
- :foo => nil,
- nil => nil
+ :staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
+ :staging_canary => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg-cny',
+ :staging_ref => 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=gstg-ref',
+ :pre => 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=pre',
+ :canary => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd',
+ :production => 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd-cny',
+ :foo => nil,
+ nil => nil
}
end
@@ -60,14 +61,14 @@ RSpec.describe QA::Support::Loglinking do
describe '.kibana_url' do
let(:url_hash) do
{
- :staging => 'https://nonprod-log.gitlab.net/',
- :staging_canary => 'https://nonprod-log.gitlab.net/',
- :staging_ref => nil,
- :pre => nil,
- :canary => 'https://log.gprd.gitlab.net/',
- :production => 'https://log.gprd.gitlab.net/',
- :foo => nil,
- nil => nil
+ :staging => 'https://nonprod-log.gitlab.net/',
+ :staging_canary => 'https://nonprod-log.gitlab.net/',
+ :staging_ref => nil,
+ :pre => nil,
+ :canary => 'https://log.gprd.gitlab.net/',
+ :production => 'https://log.gprd.gitlab.net/',
+ :foo => nil,
+ nil => nil
}
end
@@ -168,6 +169,7 @@ RSpec.describe QA::Support::Loglinking do
expect(QA::Support::Loglinking.canary?).to eq(true)
end
+
it 'and not true returns false' do
allow(QA::Support::Loglinking).to receive(:cookies).and_return({ 'gitlab_canary' => { name: 'gitlab_canary', value: 'false' } })
diff --git a/qa/spec/support/page_error_checker_spec.rb b/qa/spec/support/page_error_checker_spec.rb
index ab7014f4677..735c0f83ecd 100644
--- a/qa/spec/support/page_error_checker_spec.rb
+++ b/qa/spec/support/page_error_checker_spec.rb
@@ -113,6 +113,7 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker.parse_five_c_page_request_id(page).to_str).to eq('req678')
end
+
it 'returns nil if not present' do
allow(page).to receive(:html).and_return(error_500_no_code_str)
allow(Nokogiri::HTML).to receive(:parse).with(error_500_no_code_str).and_return(NokogiriParse.parse(error_500_no_code_str))
@@ -217,6 +218,7 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker).to receive(:report!).with(page, 404)
QA::Support::PageErrorChecker.check_page_for_error_code(page)
end
+
it 'calls report with 500 if 500 found' do
allow(page).to receive(:html).and_return(error_500_str)
allow(Nokogiri::HTML).to receive(:parse).with(error_500_str).and_return(NokogiriParse.parse(error_500_str))
@@ -224,6 +226,7 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker).to receive(:report!).with(page, 500)
QA::Support::PageErrorChecker.check_page_for_error_code(page)
end
+
it 'calls report with 500 if GDK backtrace found' do
allow(page).to receive(:html).and_return(backtrace_str)
allow(Nokogiri::HTML).to receive(:parse).with(backtrace_str).and_return(NokogiriParse.parse(backtrace_str))
@@ -231,6 +234,7 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker).to receive(:report!).with(page, 500)
QA::Support::PageErrorChecker.check_page_for_error_code(page)
end
+
it 'does not call report if 500 found in project name' do
allow(page).to receive(:html).and_return(project_name_500_str)
allow(Nokogiri::HTML).to receive(:parse).with(project_name_500_str).and_return(NokogiriParse.parse(project_name_500_str))
@@ -238,6 +242,7 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker).not_to receive(:report!)
QA::Support::PageErrorChecker.check_page_for_error_code(page)
end
+
it 'does not call report if no 404, 500 or backtrace found' do
allow(page).to receive(:html).and_return(no_error_str)
allow(Nokogiri::HTML).to receive(:parse).with(no_error_str).and_return(NokogiriParse.parse(no_error_str))
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index fe9a9c4586f..c1225964aef 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# rubocop:disable Rails/RakeEnvironment
namespace :knapsack do
desc "Run tests with knapsack runner"
task :rspec, [:rspec_args] do |_, args|
@@ -16,11 +15,26 @@ namespace :knapsack do
exit QA::Specs::KnapsackRunner.run(rspec_args)
end
- desc "Download latest knapsack report or multiple reports passed via QA_KNAPSACK_REPORTS env variable"
- task :download do
- next QA::Support::KnapsackReport.download_report unless ENV["QA_KNAPSACK_REPORTS"]
+ desc "Download latest knapsack reports for parallel jobs"
+ task :download, [:stage_name] do |_, args|
+ test_stage_name = args[:stage_name]
- ENV["QA_KNAPSACK_REPORTS"].split(",").each do |report_name|
+ # QA_KNAPSACK_REPORTS remains for changes to be backwards compatible
+ # TODO: remove and only use automated detection once changes are merged
+ unless ENV["QA_KNAPSACK_REPORTS"] || test_stage_name
+ QA::Runtime::Logger.warn("Missing QA_KNAPSACK_REPORTS environment variable or test stage name for autodetection")
+ next
+ end
+
+ reports = if test_stage_name
+ QA::Support::ParallelPipelineJobs
+ .fetch(stage_name: test_stage_name, access_token: ENV["QA_GITLAB_CI_TOKEN"])
+ .map { |job| job.tr(":", "-") }
+ else
+ ENV["QA_KNAPSACK_REPORTS"].split(",")
+ end
+
+ reports.each do |report_name|
QA::Support::KnapsackReport.new(report_name).download_report
rescue StandardError => e
QA::Runtime::Logger.error(e)
@@ -37,4 +51,3 @@ namespace :knapsack do
QA::Tools::LongRunningSpecReporter.execute
end
end
-# rubocop:enable Rails/RakeEnvironment
diff --git a/qa/tasks/reliable_report.rake b/qa/tasks/reliable_report.rake
index b4dcc2ebc01..1045fd823ab 100644
--- a/qa/tasks/reliable_report.rake
+++ b/qa/tasks/reliable_report.rake
@@ -1,8 +1,6 @@
# frozen_string_literal: true
-# rubocop:disable Rails/RakeEnvironment
desc "Fetch reliable and unreliable spec data and create report"
task :reliable_spec_report, [:range, :report_in_issue_and_slack] do |_task, args|
QA::Tools::ReliableReport.run(**args)
end
-# rubocop:enable Rails/RakeEnvironment
diff --git a/qa/tasks/vulnerabilities.rake b/qa/tasks/vulnerabilities.rake
index 79d6b8683e0..cab2f8e5ca6 100644
--- a/qa/tasks/vulnerabilities.rake
+++ b/qa/tasks/vulnerabilities.rake
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-# rubocop:disable Rails/RakeEnvironment
# How to run this rake task?
# GITLAB_QA_ACCESS_TOKEN=<access_token> GITLAB_URL="<Gitlab address>" bundle exec rake
@@ -26,4 +25,3 @@ namespace :vulnerabilities do
vuln.create_vuln_report(args[:project_id], args[:vulnerability_count].to_i)
end
end
-# rubocop:enable Rails/RakeEnvironment