summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /qa
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'qa')
-rw-r--r--qa/Gemfile8
-rw-r--r--qa/Gemfile.lock54
-rw-r--r--qa/chemlab-library-gitlab.gemspec4
-rw-r--r--qa/lib/gitlab.rb11
-rw-r--r--qa/lib/gitlab/page/admin/dashboard.rb15
-rw-r--r--qa/lib/gitlab/page/admin/dashboard.stub.rb81
-rw-r--r--qa/lib/gitlab/page/admin/subscription.rb13
-rw-r--r--qa/lib/gitlab/page/admin/subscription.stub.rb33
-rw-r--r--qa/lib/gitlab/page/group/settings/usage_quota.stub.rb227
-rw-r--r--qa/lib/gitlab/page/group/settings/usage_quotas.rb27
-rw-r--r--qa/lib/gitlab/page/main/login.rb18
-rw-r--r--qa/lib/gitlab/page/main/login.stub.rb82
-rw-r--r--qa/lib/gitlab/page/main/sign_up.rb36
-rw-r--r--qa/lib/gitlab/page/main/sign_up.stub.rb203
-rw-r--r--qa/lib/gitlab/page/subscriptions/new.rb7
-rw-r--r--qa/qa/page/component/issue_board/show.rb2
-rw-r--r--qa/qa/page/component/snippet.rb11
-rw-r--r--qa/qa/page/component/web_ide/web_terminal_panel.rb63
-rw-r--r--qa/qa/page/group/bulk_import.rb7
-rw-r--r--qa/qa/page/group/dependency_proxy.rb11
-rw-r--r--qa/qa/page/group/settings/package_registries.rb19
-rw-r--r--qa/qa/page/merge_request/new.rb7
-rw-r--r--qa/qa/page/merge_request/show.rb32
-rw-r--r--qa/qa/page/profile/two_factor_auth.rb5
-rw-r--r--qa/qa/page/project/artifact/show.rb6
-rw-r--r--qa/qa/page/project/import/repo_by_url.rb16
-rw-r--r--qa/qa/page/project/new.rb12
-rw-r--r--qa/qa/page/project/packages/show.rb2
-rw-r--r--qa/qa/page/project/registry/show.rb5
-rw-r--r--qa/qa/page/project/secure/configuration_form.rb12
-rw-r--r--qa/qa/page/project/web_ide/edit.rb3
-rw-r--r--qa/qa/resource/api_fabricator.rb34
-rw-r--r--qa/qa/resource/base.rb27
-rw-r--r--qa/qa/resource/fork.rb7
-rw-r--r--qa/qa/resource/group_badge.rb73
-rw-r--r--qa/qa/resource/group_base.rb48
-rw-r--r--qa/qa/resource/merge_request_from_fork.rb13
-rw-r--r--qa/qa/resource/personal_access_token.rb8
-rw-r--r--qa/qa/resource/project.rb54
-rw-r--r--qa/qa/resource/project_imported_from_github.rb4
-rw-r--r--qa/qa/resource/project_snippet.rb2
-rw-r--r--qa/qa/resource/snippet.rb6
-rw-r--r--qa/qa/resource/user.rb30
-rw-r--r--qa/qa/runtime/allure_report.rb32
-rw-r--r--qa/qa/runtime/api/client.rb73
-rw-r--r--qa/qa/service/praefect_manager.rb126
-rw-r--r--qa/qa/service/shellout.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb43
-rw-r--r--qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb79
-rw-r--r--qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb18
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb26
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb8
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb27
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb20
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb84
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/helm_registry_spec.rb150
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb60
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb2
-rw-r--r--qa/qa/support/api.rb114
-rw-r--r--qa/qa/support/formatters/test_stats_formatter.rb22
-rw-r--r--qa/qa/support/retrier.rb22
-rw-r--r--qa/qa/tools/delete_subgroups.rb28
-rw-r--r--qa/spec/spec_helper.rb1
-rw-r--r--qa/spec/specs/allure_report_spec.rb6
-rw-r--r--qa/spec/support/formatters/test_stats_formatter_spec.rb12
-rw-r--r--qa/spec/support/shared_contexts/packages_registry_shared_context.rb63
78 files changed, 2018 insertions, 425 deletions
diff --git a/qa/Gemfile b/qa/Gemfile
index cc2355cdfa3..ee90d049d7b 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -3,13 +3,13 @@
source 'https://rubygems.org'
gem 'gitlab-qa', require: 'gitlab/qa'
-gem 'activesupport', '~> 6.1.3.2' # This should stay in sync with the root's Gemfile
-gem 'allure-rspec', '~> 2.14.5'
+gem 'activesupport', '~> 6.1.4.1' # This should stay in sync with the root's Gemfile
+gem 'allure-rspec', '~> 2.15.0'
gem 'capybara', '~> 3.35.0'
gem 'capybara-screenshot', '~> 1.0.23'
gem 'rake', '~> 12.3.3'
gem 'rspec', '~> 3.10'
-gem 'selenium-webdriver', '~> 4.0.0.beta4'
+gem 'selenium-webdriver', '~> 4.0.0.rc1'
gem 'airborne', '~> 0.3.4', require: false # airborne is messing with rspec sandboxed mode so not requiring by default
gem 'rest-client', '~> 2.1.0'
gem 'rspec-retry', '~> 0.6.1', require: 'rspec/retry'
@@ -26,7 +26,7 @@ gem 'webdrivers', '~> 4.6'
gem 'zeitwerk', '~> 2.4'
gem 'influxdb-client', '~> 1.17'
-gem 'chemlab', '~> 0.7'
+gem 'chemlab', '~> 0.9'
gem 'chemlab-library-www-gitlab-com', '~> 0.1'
gem 'deprecation_toolkit', '~> 1.5.1', require: false
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 5f33afaa77b..153a141d3fd 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -2,7 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
abstract_type (0.0.7)
- activesupport (6.1.3.2)
+ activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -19,10 +19,10 @@ GEM
rack-test (>= 1.1.0, < 2.0)
rest-client (>= 2.0.2, < 3.0)
rspec (~> 3.8)
- allure-rspec (2.14.5)
- allure-ruby-commons (= 2.14.5)
+ allure-rspec (2.15.0)
+ allure-ruby-commons (= 2.15.0)
rspec-core (>= 3.8, < 4)
- allure-ruby-commons (2.14.5)
+ allure-ruby-commons (2.15.0)
mime-types (>= 3.3, < 4)
oj (>= 3.10, < 4)
require_all (>= 2, < 4)
@@ -41,7 +41,7 @@ GEM
capybara-screenshot (1.0.23)
capybara (>= 1.0, < 4)
launchy
- chemlab (0.7.2)
+ chemlab (0.9.1)
colorize (~> 0.8)
i18n (~> 1.8)
rake (>= 12, < 14)
@@ -81,10 +81,33 @@ GEM
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
- gitlab-qa (4.0.0)
+ ffi (1.15.4)
+ ffi-compiler (1.0.1)
+ ffi (>= 1.0.0)
+ rake
+ gitlab (4.16.1)
+ httparty (~> 0.14, >= 0.14.0)
+ terminal-table (~> 1.5, >= 1.5.1)
+ gitlab-qa (7.9.1)
+ activesupport (~> 6.1)
+ gitlab (~> 4.16.1)
+ http (= 4.3.0)
+ nokogiri (~> 1.10)
+ table_print (= 1.5.7)
+ http (4.3.0)
+ addressable (~> 2.3)
+ http-cookie (~> 1.0)
+ http-form_data (~> 2.2)
+ http-parser (~> 1.2.0)
http-accept (1.7.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
+ http-form_data (2.3.0)
+ http-parser (1.2.3)
+ ffi-compiler (>= 1.0, < 2.0)
+ httparty (0.19.0)
+ mime-types (~> 3.0)
+ multi_xml (>= 0.5.2)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
@@ -104,6 +127,7 @@ GEM
mini_mime (1.1.0)
mini_portile2 (2.5.3)
minitest (5.14.4)
+ multi_xml (0.6.0)
multipart-post (2.1.1)
netrc (0.11.0)
nokogiri (1.11.7)
@@ -112,7 +136,7 @@ GEM
octokit (4.21.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
- oj (3.13.2)
+ oj (3.13.8)
parallel (1.19.2)
parallel_tests (2.29.0)
parallel
@@ -135,7 +159,7 @@ GEM
rack-test (1.1.0)
rack (>= 1.0, < 3)
rake (12.3.3)
- regexp_parser (1.8.2)
+ regexp_parser (2.1.1)
require_all (3.0.0)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
@@ -174,11 +198,14 @@ GEM
sawyer (0.8.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
- selenium-webdriver (4.0.0.beta4)
+ selenium-webdriver (4.0.0.rc1)
childprocess (>= 0.5, < 5.0)
rexml (~> 3.2)
rubyzip (>= 1.2.2)
systemu (2.6.5)
+ table_print (1.5.7)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
timecop (0.9.1)
tzinfo (2.0.4)
@@ -186,6 +213,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
+ unicode-display_width (1.8.0)
unparser (0.4.7)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
@@ -211,12 +239,12 @@ PLATFORMS
ruby
DEPENDENCIES
- activesupport (~> 6.1.3.2)
+ activesupport (~> 6.1.4.1)
airborne (~> 0.3.4)
- allure-rspec (~> 2.14.5)
+ allure-rspec (~> 2.15.0)
capybara (~> 3.35.0)
capybara-screenshot (~> 1.0.23)
- chemlab (~> 0.7)
+ chemlab (~> 0.9)
chemlab-library-www-gitlab-com (~> 0.1)
deprecation_toolkit (~> 1.5.1)
faker (~> 2.19, >= 2.19.0)
@@ -235,7 +263,7 @@ DEPENDENCIES
rspec-retry (~> 0.6.1)
rspec_junit_formatter (~> 0.4.1)
ruby-debug-ide (~> 0.7.0)
- selenium-webdriver (~> 4.0.0.beta4)
+ selenium-webdriver (~> 4.0.0.rc1)
timecop (~> 0.9.1)
webdrivers (~> 4.6)
zeitwerk (~> 2.4)
diff --git a/qa/chemlab-library-gitlab.gemspec b/qa/chemlab-library-gitlab.gemspec
index 908aad01850..34a55ba8927 100644
--- a/qa/chemlab-library-gitlab.gemspec
+++ b/qa/chemlab-library-gitlab.gemspec
@@ -4,7 +4,7 @@ $:.unshift(File.expand_path('lib', __dir__))
Gem::Specification.new do |spec|
spec.name = 'chemlab-library-gitlab'
- spec.version = '0.1.1'
+ spec.version = '0.3.0'
spec.authors = ['GitLab Quality']
spec.email = ['quality@gitlab.com']
@@ -18,5 +18,5 @@ Gem::Specification.new do |spec|
spec.require_paths = ['lib']
- spec.add_runtime_dependency 'chemlab', '~> 0.7'
+ spec.add_runtime_dependency 'chemlab', '~> 0.9'
end
diff --git a/qa/lib/gitlab.rb b/qa/lib/gitlab.rb
index d0d1d535114..4418e51facb 100644
--- a/qa/lib/gitlab.rb
+++ b/qa/lib/gitlab.rb
@@ -1,19 +1,30 @@
# frozen_string_literal: true
+require 'chemlab/library'
+
# Chemlab Page Libraries for GitLab
module Gitlab
+ include Chemlab::Library
+
module Page
module Main
autoload :Login, 'gitlab/page/main/login'
+ autoload :SignUp, 'gitlab/page/main/sign_up'
end
module Subscriptions
autoload :New, 'gitlab/page/subscriptions/new'
end
+ module Admin
+ autoload :Dashboard, 'gitlab/page/admin/dashboard'
+ autoload :Subscription, 'gitlab/page/admin/subscription'
+ end
+
module Group
module Settings
autoload :Billing, 'gitlab/page/group/settings/billing'
+ autoload :UsageQuotas, 'gitlab/page/group/settings/usage_quotas'
end
end
end
diff --git a/qa/lib/gitlab/page/admin/dashboard.rb b/qa/lib/gitlab/page/admin/dashboard.rb
new file mode 100644
index 00000000000..f1a732f8fac
--- /dev/null
+++ b/qa/lib/gitlab/page/admin/dashboard.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Admin
+ class Dashboard < Chemlab::Page
+ path '/admin'
+
+ h2 :users_in_license
+ h2 :billable_users
+ h3 :number_of_users
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/admin/dashboard.stub.rb b/qa/lib/gitlab/page/admin/dashboard.stub.rb
new file mode 100644
index 00000000000..820acf79b9b
--- /dev/null
+++ b/qa/lib/gitlab/page/admin/dashboard.stub.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Admin
+ module Dashboard
+ # @note Defined as +h2 :users_in_license+
+ # @return [String] The text content or value of +users_in_license+
+ def users_in_license
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard.users_in_license_element).to exist
+ # end
+ # @return [Watir::H2] The raw +H2+ element
+ def users_in_license_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard).to be_users_in_license
+ # end
+ # @return [Boolean] true if the +users_in_license+ element is present on the page
+ def users_in_license?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +h2 :billable_users+
+ # @return [String] The text content or value of +billable_users+
+ def billable_users
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard.billable_users_element).to exist
+ # end
+ # @return [Watir::H2] The raw +H2+ element
+ def billable_users_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard).to be_billable_users
+ # end
+ # @return [Boolean] true if the +billable_users+ element is present on the page
+ def billable_users?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +h3 :number_of_users+
+ # @return [String] The text content or value of +number_of_users+
+ def number_of_users
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard.number_of_users_element).to exist
+ # end
+ # @return [Watir::H3] The raw +H3+ element
+ def number_of_users_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Dashboard.perform do |dashboard|
+ # expect(dashboard).to be_number_of_users
+ # end
+ # @return [Boolean] true if the +number_of_users+ element is present on the page
+ def number_of_users?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/admin/subscription.rb b/qa/lib/gitlab/page/admin/subscription.rb
new file mode 100644
index 00000000000..0f7c6b4c211
--- /dev/null
+++ b/qa/lib/gitlab/page/admin/subscription.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Admin
+ class Subscription < Chemlab::Page
+ path '/admin/subscription'
+
+ h2 :users_in_subscription
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/admin/subscription.stub.rb b/qa/lib/gitlab/page/admin/subscription.stub.rb
new file mode 100644
index 00000000000..51f23e7f0d0
--- /dev/null
+++ b/qa/lib/gitlab/page/admin/subscription.stub.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Admin
+ module Subscription
+ # @note Defined as +h2 :users_in_subscription+
+ # @return [String] The text content or value of +users_in_subscription+
+ def users_in_subscription
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Subscription.perform do |subscription|
+ # expect(subscription.users_in_subscription_element).to exist
+ # end
+ # @return [Watir::H2] The raw +H2+ element
+ def users_in_subscription_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Admin::Subscription.perform do |subscription|
+ # expect(subscription).to be_users_in_subscription
+ # end
+ # @return [Boolean] true if the +users_in_subscription+ element is present on the page
+ def users_in_subscription?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/group/settings/usage_quota.stub.rb b/qa/lib/gitlab/page/group/settings/usage_quota.stub.rb
new file mode 100644
index 00000000000..192e71e6c90
--- /dev/null
+++ b/qa/lib/gitlab/page/group/settings/usage_quota.stub.rb
@@ -0,0 +1,227 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Group
+ module Settings
+ module UsageQuota
+ # @note Defined as +link :pipeline_tab+
+ # Clicks +pipeline_tab+
+ def pipeline_tab
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.pipeline_tab_element).to exist
+ # end
+ # @return [Watir::Link] The raw +Link+ element
+ def pipeline_tab_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_pipeline_tab
+ # end
+ # @return [Boolean] true if the +pipeline_tab+ element is present on the page
+ def pipeline_tab?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +link :storage_tab+
+ # Clicks +storage_tab+
+ def storage_tab
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.storage_tab_element).to exist
+ # end
+ # @return [Watir::Link] The raw +Link+ element
+ def storage_tab_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_storage_tab
+ # end
+ # @return [Boolean] true if the +storage_tab+ element is present on the page
+ def storage_tab?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +link :buy_ci_minutes+
+ # Clicks +buy_ci_minutes+
+ def buy_ci_minutes
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.buy_ci_minutes_element).to exist
+ # end
+ # @return [Watir::Link] The raw +Link+ element
+ def buy_ci_minutes_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_buy_ci_minutes
+ # end
+ # @return [Boolean] true if the +buy_ci_minutes+ element is present on the page
+ def buy_ci_minutes?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +link :buy_storage+
+ # Clicks +buy_storage+
+ def buy_storage
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.buy_storage_element).to exist
+ # end
+ # @return [Watir::Link] The raw +Link+ element
+ def buy_storage_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_buy_storage
+ # end
+ # @return [Boolean] true if the +buy_storage+ element is present on the page
+ def buy_storage?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +strong :additional_minutes+
+ # @return [String] The text content or value of +additional_minutes+
+ def additional_minutes
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.additional_minutes_element).to exist
+ # end
+ # @return [Watir::Strong] The raw +Strong+ element
+ def additional_minutes_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_additional_minutes
+ # end
+ # @return [Boolean] true if the +additional_minutes+ element is present on the page
+ def additional_minutes?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +div :additional_minutes_usage+
+ # @return [String] The text content or value of +additional_minutes_usage+
+ def additional_minutes_usage
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.additional_minutes_usage_element).to exist
+ # end
+ # @return [Watir::Div] The raw +Div+ element
+ def additional_minutes_usage_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_additional_minutes_usage
+ # end
+ # @return [Boolean] true if the +additional_minutes_usage+ element is present on the page
+ def additional_minutes_usage?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +strong :plan_minutes+
+ # @return [String] The text content or value of +plan_minutes+
+ def plan_minutes
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.plan_minutes_element).to exist
+ # end
+ # @return [Watir::Strong] The raw +Strong+ element
+ def plan_minutes_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_plan_minutes
+ # end
+ # @return [Boolean] true if the +plan_minutes+ element is present on the page
+ def plan_minutes?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +div :plan_minutes_usage+
+ # @return [String] The text content or value of +plan_minutes_usage+
+ def plan_minutes_usage
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.plan_minutes_usage_element).to exist
+ # end
+ # @return [Watir::Div] The raw +Div+ element
+ def plan_minutes_usage_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_plan_minutes_usage
+ # end
+ # @return [Boolean] true if the +plan_minutes_usage+ element is present on the page
+ def plan_minutes_usage?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +div :purchase_successful_alert+
+ # @return [String] The text content or value of +purchase_successful_alert+
+ def purchase_successful_alert
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota.purchase_successful_alert_element).to exist
+ # end
+ # @return [Watir::Div] The raw +Div+ element
+ def purchase_successful_alert_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Group::Settings::UsageQuota.perform do |usage_quota|
+ # expect(usage_quota).to be_purchase_successful_alert
+ # end
+ # @return [Boolean] true if the +purchase_successful_alert+ element is present on the page
+ def purchase_successful_alert?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
new file mode 100644
index 00000000000..455a695f703
--- /dev/null
+++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Group
+ module Settings
+ class UsageQuotas < Chemlab::Page
+ link :pipeline_tab, id: 'pipelines-quota'
+ link :storage_tab, id: 'storage-quota'
+ link :buy_ci_minutes, text: 'Buy additional minutes'
+ link :buy_storage, text: /Purchase more storage/
+ strong :additional_minutes, text: 'Additional minutes'
+ div(:additional_minutes_usage) { additional_minutes_element.following_sibling.span }
+ div :purchase_successful_alert, text: /You have successfully purchased CI minutes/
+
+ def plan_minutes_limits
+ plan_minutes_usage[%r{([^/ ]+)$}]
+ end
+
+ def additional_limits
+ additional_minutes_usage[%r{([^/ ]+)$}]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/main/login.rb b/qa/lib/gitlab/page/main/login.rb
index 9f20a040550..de05df1a086 100644
--- a/qa/lib/gitlab/page/main/login.rb
+++ b/qa/lib/gitlab/page/main/login.rb
@@ -10,11 +10,27 @@ module Gitlab
text_field :password_field
button :sign_in_button
- def sign_in_as(username:, password:)
+ button :accept_terms, text: 'Accept terms'
+
+ # password change tab
+ text_field :password_confirmation_field
+ button :change_password_button
+
+ # Sign in using a given username and password
+ # @note this will also automatically accept terms if prompted
+ # @param [String] username the username to sign in with
+ # @param [String] password the password to sign in with
+ # @example
+ # Page::Main::Login.perform do |login|
+ # login.sign_in_as(username: 'username', password: 'password')
+ # login.sign_in_as(username: 'username', password: 'password', accept_terms: false)
+ # end
+ def sign_in_as(username:, password:, accept_terms: true)
self.login_field = username
self.password_field = password
sign_in_button
+ self.accept_terms if accept_terms && accept_terms?
end
end
end
diff --git a/qa/lib/gitlab/page/main/login.stub.rb b/qa/lib/gitlab/page/main/login.stub.rb
index a4cef291616..a819ca4bcc8 100644
--- a/qa/lib/gitlab/page/main/login.stub.rb
+++ b/qa/lib/gitlab/page/main/login.stub.rb
@@ -95,6 +95,88 @@ module Gitlab
def sign_in_button?
# This is a stub, used for indexing. The method is dynamically generated.
end
+
+ # @note Defined as +button :accept_terms+
+ # Clicks +accept_terms+
+ def accept_terms
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login.accept_terms_element).to exist
+ # end
+ # @return [Watir::Button] The raw +Button+ element
+ def accept_terms_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login).to be_accept_terms
+ # end
+ # @return [Boolean] true if the +accept_terms+ element is present on the page
+ def accept_terms?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +text_field :password_confirmation_field+
+ # @return [String] The text content or value of +password_confirmation_field+
+ def password_confirmation_field
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of password_confirmation_field
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # login.password_confirmation_field = 'value'
+ # end
+ # @param value [String] The value to set.
+ def password_confirmation_field=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login.password_confirmation_field_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def password_confirmation_field_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login).to be_password_confirmation_field
+ # end
+ # @return [Boolean] true if the +password_confirmation_field+ element is present on the page
+ def password_confirmation_field?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +button :change_password_button+
+ # Clicks +change_password_button+
+ def change_password_button
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login.change_password_button_element).to exist
+ # end
+ # @return [Watir::Button] The raw +Button+ element
+ def change_password_button_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::Login.perform do |login|
+ # expect(login).to be_change_password_button
+ # end
+ # @return [Boolean] true if the +change_password_button+ element is present on the page
+ def change_password_button?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
end
end
end
diff --git a/qa/lib/gitlab/page/main/sign_up.rb b/qa/lib/gitlab/page/main/sign_up.rb
new file mode 100644
index 00000000000..85d7f482461
--- /dev/null
+++ b/qa/lib/gitlab/page/main/sign_up.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Main
+ class SignUp < Chemlab::Page
+ path '/users/sign_up'
+
+ # TODO: Refactor data-qa-selectors to be more terse
+ text_field :first_name, 'data-qa-selector': 'new_user_first_name_field'
+ text_field :last_name, 'data-qa-selector': 'new_user_last_name_field'
+
+ text_field :username, 'data-qa-selector': 'new_user_username_field'
+
+ text_field :email, 'data-qa-selector': 'new_user_email_field'
+ text_field :password, 'data-qa-selector': 'new_user_password_field'
+
+ button :register, 'data-qa-selector': 'new_user_register_button'
+
+ # Register a user
+ # @param [Resource::User] user the user to register
+ def register_user(user)
+ raise ArgumentError, 'User must be of type Resource::User' unless user.is_a? ::QA::Resource::User
+
+ self.first_name = user.first_name
+ self.last_name = user.last_name
+ self.username = user.username
+ self.email = user.email
+ self.password = user.password
+
+ self.register
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/main/sign_up.stub.rb b/qa/lib/gitlab/page/main/sign_up.stub.rb
new file mode 100644
index 00000000000..881bd922c45
--- /dev/null
+++ b/qa/lib/gitlab/page/main/sign_up.stub.rb
@@ -0,0 +1,203 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Page
+ module Main
+ module SignUp
+ # @note Defined as +text_field :first_name+
+ # @return [String] The text content or value of +first_name+
+ def first_name
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of first_name
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # sign_up.first_name = 'value'
+ # end
+ # @param value [String] The value to set.
+ def first_name=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.first_name_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def first_name_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_first_name
+ # end
+ # @return [Boolean] true if the +first_name+ element is present on the page
+ def first_name?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +text_field :last_name+
+ # @return [String] The text content or value of +last_name+
+ def last_name
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of last_name
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # sign_up.last_name = 'value'
+ # end
+ # @param value [String] The value to set.
+ def last_name=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.last_name_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def last_name_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_last_name
+ # end
+ # @return [Boolean] true if the +last_name+ element is present on the page
+ def last_name?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +text_field :username+
+ # @return [String] The text content or value of +username+
+ def username
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of username
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # sign_up.username = 'value'
+ # end
+ # @param value [String] The value to set.
+ def username=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.username_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def username_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_username
+ # end
+ # @return [Boolean] true if the +username+ element is present on the page
+ def username?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +text_field :email+
+ # @return [String] The text content or value of +email+
+ def email
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of email
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # sign_up.email = 'value'
+ # end
+ # @param value [String] The value to set.
+ def email=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.email_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def email_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_email
+ # end
+ # @return [Boolean] true if the +email+ element is present on the page
+ def email?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +text_field :password+
+ # @return [String] The text content or value of +password+
+ def password
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # Set the value of password
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # sign_up.password = 'value'
+ # end
+ # @param value [String] The value to set.
+ def password=(value)
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.password_element).to exist
+ # end
+ # @return [Watir::TextField] The raw +TextField+ element
+ def password_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_password
+ # end
+ # @return [Boolean] true if the +password+ element is present on the page
+ def password?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @note Defined as +button :register+
+ # Clicks +register+
+ def register
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up.register_element).to exist
+ # end
+ # @return [Watir::Button] The raw +Button+ element
+ def register_element
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+
+ # @example
+ # Gitlab::Page::Main::SignUp.perform do |sign_up|
+ # expect(sign_up).to be_register
+ # end
+ # @return [Boolean] true if the +register+ element is present on the page
+ def register?
+ # This is a stub, used for indexing. The method is dynamically generated.
+ end
+ end
+ end
+ end
+end
diff --git a/qa/lib/gitlab/page/subscriptions/new.rb b/qa/lib/gitlab/page/subscriptions/new.rb
index 4c0e5446444..6e3cb45fd29 100644
--- a/qa/lib/gitlab/page/subscriptions/new.rb
+++ b/qa/lib/gitlab/page/subscriptions/new.rb
@@ -6,10 +6,11 @@ module Gitlab
class New < Chemlab::Page
path '/subscriptions/new'
- # Subscription Details
+ # Purchase Details
select :plan_name
select :group_name
text_field :number_of_users
+ text_field :quantity
button :continue_to_billing, text: /Continue to billing/
# Billing address
@@ -35,6 +36,10 @@ module Gitlab
# Confirmation
button :confirm_purchase, text: /Confirm purchase/
+
+ # Order Summary
+ div :selected_plan, 'data-testid': 'selected-plan'
+ div :order_total, 'data-testid': 'total-amount'
end
end
end
diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb
index 1c1f7ab17f3..4b842412c0f 100644
--- a/qa/qa/page/component/issue_board/show.rb
+++ b/qa/qa/page/component/issue_board/show.rb
@@ -48,7 +48,7 @@ module QA
# with the attribute `data-qa-selector` since such element is not unique when the
# `is-focused` class is not set, and it was not possible to find a better solution.
def focused_board
- find('.issue-boards-content.js-focus-mode-board.is-focused')
+ find('.js-focus-mode-board.is-focused')
end
def boards_dropdown
diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb
index 73f41e0aa51..ad264bd6d56 100644
--- a/qa/qa/page/component/snippet.rb
+++ b/qa/qa/page/component/snippet.rb
@@ -79,6 +79,11 @@ module QA
element :default_actions_container
element :copy_contents_button
end
+
+ base.view 'app/views/layouts/nav/_breadcrumbs.html.haml' do
+ element :breadcrumb_links_content
+ element :breadcrumb_sub_title_content
+ end
end
def has_snippet_title?(snippet_title)
@@ -249,6 +254,12 @@ module QA
raise ElementNotFound, "Comment did not appear as expected"
end
end
+
+ def snippet_id
+ within_element(:breadcrumb_links_content) do
+ find_element(:breadcrumb_sub_title_content).text.delete_prefix('$')
+ end
+ end
end
end
end
diff --git a/qa/qa/page/component/web_ide/web_terminal_panel.rb b/qa/qa/page/component/web_ide/web_terminal_panel.rb
new file mode 100644
index 00000000000..80f83bcff7c
--- /dev/null
+++ b/qa/qa/page/component/web_ide/web_terminal_panel.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module WebIDE
+ module WebTerminalPanel
+ extend QA::Page::PageConcern
+
+ def self.prepended(base)
+ super
+
+ base.class_eval do
+ view 'app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue' do
+ element :ide_right_sidebar, %q(:data-qa-selector="`ide_${side}_sidebar`") # rubocop:disable QA/ElementWithPattern
+ end
+
+ view 'app/assets/javascripts/ide/components/ide_sidebar_nav.vue' do
+ element :terminal_tab_button, %q(:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`") # rubocop:disable QA/ElementWithPattern
+ end
+
+ view 'app/assets/javascripts/ide/components/terminal/empty_state.vue' do
+ element :start_web_terminal_button
+ end
+
+ view 'app/assets/javascripts/ide/components/terminal/terminal.vue' do
+ element :loading_container
+ element :terminal_screen
+ end
+ end
+ end
+
+ def has_finished_loading?
+ has_no_element?(:loading_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
+ end
+
+ def has_terminal_screen?
+ wait_until(reload: false) do
+ within_element :terminal_screen do
+ # The DOM initially just includes the :terminal_screen element
+ # and then the xterm package dynamically loads when the user
+ # clicks the Start Web Terminal button. If it loads succesfully
+ # an element with the class `xterm` is added to the DOM.
+ # The xterm is a third-party library, so we can't add a selector
+ find(".xterm")
+ end
+ end
+ end
+
+ def start_web_terminal
+ within_element :ide_right_sidebar do
+ click_element :terminal_tab_button
+ end
+
+ click_element :start_web_terminal_button
+
+ has_element? :loading_container, text: "Starting"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/group/bulk_import.rb b/qa/qa/page/group/bulk_import.rb
index b9497aeb6e5..a62823f3469 100644
--- a/qa/qa/page/group/bulk_import.rb
+++ b/qa/qa/page/group/bulk_import.rb
@@ -33,7 +33,12 @@ module QA
within_element(:import_item, source_group: source_group_name) do
click_element(:target_namespace_selector_dropdown)
click_element(:target_group_dropdown_item, group_name: target_group_name)
- click_element(:import_group_button)
+
+ retry_until do
+ click_element(:import_group_button)
+ # Make sure import started before waiting for completion
+ has_no_element?(:import_status_indicator, text: "Not started", wait: 1)
+ end
end
end
diff --git a/qa/qa/page/group/dependency_proxy.rb b/qa/qa/page/group/dependency_proxy.rb
index f637c79cffc..fa37e8eac83 100644
--- a/qa/qa/page/group/dependency_proxy.rb
+++ b/qa/qa/page/group/dependency_proxy.rb
@@ -4,19 +4,10 @@ module QA
module Page
module Group
class DependencyProxy < QA::Page::Base
- view 'app/views/groups/dependency_proxies/show.html.haml' do
- element :dependency_proxy_setting_toggle
- end
-
- view 'app/views/groups/dependency_proxies/_url.html.haml' do
+ view 'app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue' do
element :dependency_proxy_count
end
- def has_dependency_proxy_enabled?
- toggle = find_element(:dependency_proxy_setting_toggle)
- toggle[:class].include?('is-checked')
- end
-
def has_blob_count?(blob_text)
has_element?(:dependency_proxy_count, text: blob_text)
end
diff --git a/qa/qa/page/group/settings/package_registries.rb b/qa/qa/page/group/settings/package_registries.rb
index 8a2802b0035..5c93c0d6222 100644
--- a/qa/qa/page/group/settings/package_registries.rb
+++ b/qa/qa/page/group/settings/package_registries.rb
@@ -7,15 +7,17 @@ module QA
class PackageRegistries < QA::Page::Base
include ::QA::Page::Settings::Common
- view 'app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue' do
+ view 'app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue' do
element :package_registry_settings_content
- end
-
- view 'app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue' do
element :allow_duplicates_toggle
element :allow_duplicates_label
end
+ view 'app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue' do
+ element :dependency_proxy_settings_content
+ element :dependency_proxy_setting_toggle
+ end
+
def set_allow_duplicates_disabled
expand_content :package_registry_settings_content do
click_element(:allow_duplicates_toggle) if duplicates_enabled?
@@ -35,6 +37,15 @@ module QA
def duplicates_disabled?
has_element?(:allow_duplicates_label, text: 'Do not allow duplicates')
end
+
+ def has_dependency_proxy_enabled?
+ expand_content :dependency_proxy_settings_content do
+ within_element :dependency_proxy_setting_toggle do
+ toggle = find('button.gl-toggle')
+ toggle[:class].include?('is-checked')
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index 71e51ddd504..bcc60a8275d 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -20,6 +20,13 @@ module QA
element :file_name_content
end
+ def has_secure_description?(scanner_name)
+ scanner_url_name = scanner_name.downcase.tr('_', '-')
+ "Configure #{scanner_name} in `.gitlab-ci.yml` using the GitLab managed template. You can " \
+ "[add variable overrides](https://docs.gitlab.com/ee/user/application_security/#{scanner_url_name}/#customizing-the-#{scanner_url_name}-settings) " \
+ "to customize #{scanner_name} settings."
+ end
+
def create_merge_request
click_element(:issuable_create_button, Page::MergeRequest::Show)
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 1d8d9ed6859..d4fa3b38f02 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -25,6 +25,11 @@ module QA
view 'app/assets/javascripts/diffs/components/compare_versions.vue' do
element :target_version_dropdown
+ element :file_tree_button
+ end
+
+ view 'app/assets/javascripts/diffs/components/tree_list.vue' do
+ element :file_tree_container
end
view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
@@ -93,7 +98,6 @@ module QA
end
view 'app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue' do
- element :apply_suggestions_batch_button
element :add_suggestion_batch_button
end
@@ -187,11 +191,17 @@ module QA
end
def has_file?(file_name)
- has_element?(:file_name_content, text: file_name)
+ open_file_tree
+ has_element?(:file_name_content, file_name: file_name)
end
def has_no_file?(file_name)
- has_no_element?(:file_name_content, text: file_name)
+ open_file_tree
+ has_no_element?(:file_name_content, file_name: file_name)
+ end
+
+ def open_file_tree
+ click_element(:file_tree_button) unless has_element?(:file_tree_container)
end
def has_merge_button?
@@ -202,7 +212,7 @@ module QA
def has_pipeline_status?(text)
# Pipelines can be slow, so we wait a bit longer than the usual 10 seconds
- wait_until(sleep_interval: 5, reload: false) do
+ wait_until(max_duration: 120, sleep_interval: 5, reload: true) do
has_element?(:merge_request_pipeline_info_content, text: text, wait: 15 )
end
end
@@ -288,13 +298,11 @@ module QA
end
def merge_immediately!
- merge_moment_dropdown_found = has_element?(:merge_moment_dropdown, wait: 0)
-
- if merge_moment_dropdown_found
- click_element(:merge_moment_dropdown)
- click_element(:merge_immediately_menu_item)
+ if has_element?(:merge_moment_dropdown)
+ click_element(:merge_moment_dropdown, skip_finished_loading_check: true)
+ click_element(:merge_immediately_menu_item, skip_finished_loading_check: true)
else
- click_element(:merge_button)
+ click_element(:merge_button, skip_finished_loading_check: true)
end
end
@@ -355,10 +363,6 @@ module QA
all_elements(:add_suggestion_batch_button, minimum: 1).first.click
end
- def apply_suggestions_batch
- all_elements(:apply_suggestions_batch_button, minimum: 1).first.click
- end
-
def cherry_pick!
click_element(:cherry_pick_button, Page::Component::CommitModal)
click_element(:submit_commit_button)
diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb
index a0dd230d8ab..63593bf0482 100644
--- a/qa/qa/page/profile/two_factor_auth.rb
+++ b/qa/qa/page/profile/two_factor_auth.rb
@@ -11,6 +11,7 @@ module QA
view 'app/views/profiles/two_factor_auths/show.html.haml' do
element :otp_secret_content
element :pin_code_field
+ element :current_password_field
element :register_2fa_app_button
end
@@ -33,6 +34,10 @@ module QA
fill_element(:pin_code_field, pin_code)
end
+ def set_current_password(password)
+ fill_element(:current_password_field, password)
+ end
+
def click_register_2fa_app_button
click_element :register_2fa_app_button
end
diff --git a/qa/qa/page/project/artifact/show.rb b/qa/qa/page/project/artifact/show.rb
index 437363d4a98..5e05eec79f0 100644
--- a/qa/qa/page/project/artifact/show.rb
+++ b/qa/qa/page/project/artifact/show.rb
@@ -9,8 +9,10 @@ module QA
element :directory_name_link
end
- def go_to_directory(name)
- click_element(:directory_name_link, directory_name: name)
+ def go_to_directory(name, retry_attempts = 1)
+ retry_on_exception(max_attempts: retry_attempts, reload: true, sleep_interval: 10) do
+ click_element(:directory_name_link, directory_name: name)
+ end
end
end
end
diff --git a/qa/qa/page/project/import/repo_by_url.rb b/qa/qa/page/project/import/repo_by_url.rb
index 0e7524a181a..4a8d08d6499 100644
--- a/qa/qa/page/project/import/repo_by_url.rb
+++ b/qa/qa/page/project/import/repo_by_url.rb
@@ -5,10 +5,9 @@ module QA
module Project
module Import
class RepoByURL < Page::Base
- include Page::Component::Select2
-
- view 'app/views/projects/_new_project_fields.html.haml' do
+ view 'app/assets/javascripts/projects/new/components/new_project_url_select.vue' do
element :select_namespace_dropdown
+ element :select_namespace_dropdown_search_field
end
def import!(gitlab_repo_path, name)
@@ -33,8 +32,15 @@ module QA
end
def choose_test_namespace
- find('.js-select-namespace').click
- search_and_select(Runtime::Namespace.path)
+ choose_namespace(Runtime::Namespace.path)
+ end
+
+ def choose_namespace(namespace)
+ retry_on_exception do
+ click_element :select_namespace_dropdown
+ fill_element :select_namespace_dropdown_search_field, namespace
+ click_button namespace
+ end
end
def click_create_button
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 06e476f009a..3ecdabeeed2 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -5,7 +5,6 @@ module QA
module Project
class New < Page::Base
include Page::Component::Project::Templates
- include Page::Component::Select2
include Page::Component::VisibilitySetting
include Layout::Flash
@@ -14,7 +13,6 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :initialize_with_readme_checkbox
- element :project_namespace_select
element :project_namespace_field, 'namespaces_options' # rubocop:disable QA/ElementWithPattern
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
@@ -28,6 +26,11 @@ module QA
element :template_option_row
end
+ view 'app/assets/javascripts/projects/new/components/new_project_url_select.vue' do
+ element :select_namespace_dropdown
+ element :select_namespace_dropdown_search_field
+ end
+
view 'app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue' do
element :panel_link
end
@@ -46,8 +49,9 @@ module QA
def choose_namespace(namespace)
retry_on_exception do
- click_element :project_namespace_select unless dropdown_open?
- search_and_select(namespace)
+ click_element :select_namespace_dropdown
+ fill_element :select_namespace_dropdown_search_field, namespace
+ click_button namespace
end
end
diff --git a/qa/qa/page/project/packages/show.rb b/qa/qa/page/project/packages/show.rb
index 59e9a3752c7..4872c0bc705 100644
--- a/qa/qa/page/project/packages/show.rb
+++ b/qa/qa/page/project/packages/show.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Packages
class Show < QA::Page::Base
- view 'app/assets/javascripts/packages/details/components/app.vue' do
+ view 'app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue' do
element :delete_button
element :delete_modal_button
element :package_information_content
diff --git a/qa/qa/page/project/registry/show.rb b/qa/qa/page/project/registry/show.rb
index 03c547fc8b5..f2472a83401 100644
--- a/qa/qa/page/project/registry/show.rb
+++ b/qa/qa/page/project/registry/show.rb
@@ -10,6 +10,10 @@ module QA
end
view 'app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue' do
+ element :more_actions_menu
+ end
+
+ view 'app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue' do
element :tag_delete_button
end
@@ -30,6 +34,7 @@ module QA
end
def click_delete
+ click_element(:more_actions_menu)
click_element(:tag_delete_button)
find_button('Delete').click
end
diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb
index 73d1601b61e..3e89a57e870 100644
--- a/qa/qa/page/project/secure/configuration_form.rb
+++ b/qa/qa/page/project/secure/configuration_form.rb
@@ -9,19 +9,31 @@ module QA
include QA::Page::Settings::Common
view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do
+ element :dependency_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
element :sast_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern
+ element :dependency_scanning_mr_button, "`${feature.type}_mr_button`" # rubocop:disable QA/ElementWithPattern
end
def click_sast_enable_button
click_element(:sast_enable_button)
end
+ def click_dependency_scanning_mr_button
+ click_element(:dependency_scanning_mr_button)
+ end
+
def has_sast_status?(status_text)
within_element(:sast_status) do
has_text?(status_text)
end
end
+
+ def has_dependency_scanning_status?(status_text)
+ within_element(:dependency_scanning_status) do
+ has_text?(status_text)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 78b2db7d723..9c0a3ab691c 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -6,6 +6,7 @@ module QA
module WebIDE
class Edit < Page::Base
prepend Page::Component::WebIDE::Alert
+ prepend Page::Component::WebIDE::WebTerminalPanel
include Page::Component::DropdownFilter
view 'app/assets/javascripts/ide/components/activity_bar.vue' do
@@ -330,5 +331,3 @@ module QA
end
end
end
-
-QA::Page::Project::WebIDE::Edit.prepend_mod_with('Page::Component::WebIDE::WebTerminalPanel', namespace: QA)
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index c1533577657..b94fa543b48 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -96,31 +96,35 @@ module QA
end
def api_post
- if api_post_path == "/graphql"
- graphql_response = post(
- Runtime::API::Request.new(api_client, api_post_path).url,
- query: api_post_body)
+ process_api_response(api_post_to(api_post_path, api_post_body))
+ end
+
+ def api_post_to(post_path, post_body)
+ if post_path == "/graphql"
+ graphql_response = post(Runtime::API::Request.new(api_client, post_path).url, query: post_body)
- flattened_response = flatten_hash(parse_body(graphql_response))
+ body = flatten_hash(parse_body(graphql_response))
- unless graphql_response.code == HTTP_STATUS_OK && flattened_response[:errors].empty?
- raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`."
+ unless graphql_response.code == HTTP_STATUS_OK && (body[:errors].nil? || body[:errors].empty?)
+ raise(ResourceFabricationFailedError, <<~MSG)
+ Fabrication of #{self.class.name} using the API failed (#{graphql_response.code}) with `#{graphql_response}`.
+ MSG
end
- flattened_response[:web_url] = flattened_response.delete(:webUrl)
- flattened_response[:id] = flattened_response.fetch(:id).split('/')[-1]
+ body[:id] = body.fetch(:id).split('/').last
- process_api_response(flattened_response)
+ body.transform_keys { |key| key.to_s.underscore.to_sym }
else
- response = post(
- Runtime::API::Request.new(api_client, api_post_path).url,
- api_post_body)
+ response = post(Runtime::API::Request.new(api_client, post_path).url, post_body)
unless response.code == HTTP_STATUS_CREATED
- raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ raise(
+ ResourceFabricationFailedError,
+ "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ )
end
- process_api_response(parse_body(response))
+ parse_body(response)
end
end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 2848e3ba7d2..a7243b7ebc2 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -100,7 +100,9 @@ module QA
attr_writer(name)
define_method(name) do
- instance_variable_get("@#{name}") || instance_variable_set("@#{name}", populate_attribute(name, block))
+ return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
+
+ instance_variable_set("@#{name}", attribute_value(name, block))
end
end
@@ -121,9 +123,7 @@ module QA
return self unless api_resource
all_attributes.each do |attribute_name|
- api_value = api_resource[attribute_name]
-
- instance_variable_set("@#{attribute_name}", api_value) if api_value
+ instance_variable_set("@#{attribute_name}", api_resource[attribute_name]) if api_resource.key?(attribute_name)
end
self
@@ -160,20 +160,17 @@ module QA
private
- def populate_attribute(name, block)
- value = attribute_value(name, block)
-
- raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
-
- value
- end
-
def attribute_value(name, block)
- api_value = api_resource&.dig(name)
+ no_api_value = !api_resource&.key?(name)
+ raise NoValueError, "No value was computed for #{name} of #{self.class.name}." if no_api_value && !block
- log_having_both_api_result_and_block(name, api_value) if api_value && block
+ unless no_api_value
+ api_value = api_resource[name]
+ log_having_both_api_result_and_block(name, api_value) if block
+ return api_value
+ end
- api_value || (block && instance_exec(&block))
+ instance_exec(&block)
end
# Get all defined attributes across all parents
diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb
index 106d1d5548a..b3814011f2c 100644
--- a/qa/qa/resource/fork.rb
+++ b/qa/qa/resource/fork.rb
@@ -14,6 +14,7 @@ module QA
resource.add_name_uuid = false
resource.name = name
resource.path_with_namespace = "#{user.username}/#{name}"
+ resource.api_client = @api_client
end
end
@@ -69,6 +70,12 @@ module QA
populate(:project)
end
+ def remove_via_api!
+ project.remove_via_api!
+ upstream.remove_via_api!
+ user.remove_via_api! unless Specs::Helpers::ContextSelector.dot_com?
+ end
+
def api_get_path
"/projects/#{CGI.escape(path_with_namespace)}"
end
diff --git a/qa/qa/resource/group_badge.rb b/qa/qa/resource/group_badge.rb
new file mode 100644
index 00000000000..fd76f066e8b
--- /dev/null
+++ b/qa/qa/resource/group_badge.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class GroupBadge < Base
+ attributes :id,
+ :name,
+ :link_url,
+ :image_url,
+ :group
+
+ # API get path
+ #
+ # @return [String]
+ def api_get_path
+ "/groups/#{CGI.escape(group.full_path)}/badges/#{id}"
+ end
+
+ # API post path
+ #
+ # @return [String]
+ def api_post_path
+ "/groups/#{CGI.escape(group.full_path)}/badges"
+ end
+
+ # Params for label creation
+ #
+ # @return [Hash]
+ def api_post_body
+ {
+ link_url: link_url,
+ image_url: image_url
+ }
+ end
+
+ # Override base method as this particular resource does not expose a web_url property
+ #
+ # @param [Hash] resource
+ # @return [String]
+ def resource_web_url(_resource); end
+
+ # Object comparison
+ #
+ # @param [QA::Resource::GroupBadge] other
+ # @return [Boolean]
+ def ==(other)
+ other.is_a?(GroupBadge) && comparable_badge == other.comparable_badge
+ end
+
+ # Override inspect for a better rspec failure diff output
+ #
+ # @return [String]
+ def inspect
+ JSON.pretty_generate(comparable_badge)
+ end
+
+ protected
+
+ # Return subset of fields for comparing badges
+ #
+ # @return [Hash]
+ def comparable_badge
+ reload! unless api_response
+
+ api_response.slice(
+ :name,
+ :link_url,
+ :image_url
+ )
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb
index a1e5b19f409..932d39675a3 100644
--- a/qa/qa/resource/group_base.rb
+++ b/qa/qa/resource/group_base.rb
@@ -14,6 +14,22 @@ module QA
:name,
:full_path
+ # Get group projects
+ #
+ # @return [Array<QA::Resource::Project>]
+ def projects
+ parse_body(api_get_from("#{api_get_path}/projects")).map do |project|
+ Project.init do |resource|
+ resource.api_client = api_client
+ resource.group = self
+ resource.id = project[:id]
+ resource.name = project[:name]
+ resource.description = project[:description]
+ resource.path_with_namespace = project[:path_with_namespace]
+ end
+ end
+ end
+
# Get group labels
#
# @return [Array<QA::Resource::GroupLabel>]
@@ -46,6 +62,38 @@ module QA
end
end
+ # Get group badges
+ #
+ # @return [Array<QA::Resource::GroupBadge>]
+ def badges
+ parse_body(api_get_from("#{api_get_path}/badges")).map do |badge|
+ GroupBadge.init do |resource|
+ resource.api_client = api_client
+ resource.group = self
+ resource.id = badge[:id]
+ resource.name = badge[:name]
+ resource.link_url = badge[:link_url]
+ resource.image_url = badge[:image_url]
+ end
+ end
+ end
+
+ # Get group members
+ #
+ # @return [Array<QA::Resource::User>]
+ def members
+ parse_body(api_get_from("#{api_get_path}/members")).map do |member|
+ User.init do |resource|
+ resource.api_client = api_client
+ resource.id = member[:id]
+ resource.name = member[:name]
+ resource.username = member[:username]
+ resource.email = member[:email]
+ resource.access_level = member[:access_level]
+ end
+ end
+ end
+
# API get path
#
# @return [String]
diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb
index 4eebbdf0a52..b0579cf37b8 100644
--- a/qa/qa/resource/merge_request_from_fork.rb
+++ b/qa/qa/resource/merge_request_from_fork.rb
@@ -6,7 +6,7 @@ module QA
attr_accessor :fork_branch
attribute :fork do
- Fork.fabricate_via_browser_ui!
+ Fork.fabricate_via_api!
end
attribute :push do
@@ -23,8 +23,15 @@ module QA
fork.project.visit!
- Page::Project::Show.perform(&:new_merge_request)
- Page::MergeRequest::New.perform(&:create_merge_request)
+ mr_url = Flow::Login.while_signed_in(as: fork.user) do
+ Page::Project::Show.perform(&:new_merge_request)
+ Page::MergeRequest::New.perform(&:create_merge_request)
+
+ current_url
+ end
+
+ Flow::Login.sign_in
+ visit(mr_url)
end
def fabricate_via_api!
diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb
index 924e4206166..d992d7987b4 100644
--- a/qa/qa/resource/personal_access_token.rb
+++ b/qa/qa/resource/personal_access_token.rb
@@ -8,7 +8,7 @@ module QA
attr_accessor :name
# The user for which the personal access token is to be created
- # This *could* be different than the api_client.user or the api_user provided by the QA::Resource::ApiFabricator module
+ # This *could* be different than the api_client.user or the api_user provided by the QA::Resource::ApiFabricator
attr_writer :user
attribute :token
@@ -17,7 +17,9 @@ module QA
# If Runtime::Env.admin_personal_access_token is provided, fabricate via the API,
# else, fabricate via the browser.
def fabricate_via_api!
- @token = QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username)
+ QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username).tap do |cached_token|
+ @token = cached_token if cached_token
+ end
return if @token
resource = if Runtime::Env.admin_personal_access_token && !@user.nil?
@@ -28,7 +30,7 @@ module QA
fabricate!
end
- QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, self.token)
+ QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, token)
resource
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 5ad55090f8c..3f6a4eee5ac 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -12,13 +12,13 @@ module QA
:auto_devops_enabled,
:github_personal_access_token,
:github_repository_path,
- :gitlab_repository_path
+ :gitlab_repository_path,
+ :personal_namespace
attributes :id,
:name,
:add_name_uuid,
:description,
- :personal_namespace,
:runners_token,
:visibility,
:template_name,
@@ -31,13 +31,15 @@ module QA
end
attribute :path_with_namespace do
- "#{group.full_path}/#{name}"
+ "#{personal_namespace || group.full_path}/#{name}"
end
alias_method :full_path, :path_with_namespace
def sandbox_path
- group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : ''
+ return '' if personal_namespace || !group.respond_to?('sandbox')
+
+ "#{group.sandbox.path}/"
end
attribute :repository_ssh_location do
@@ -50,12 +52,12 @@ module QA
def initialize
@add_name_uuid = true
- @personal_namespace = false
@description = 'My awesome project'
@initialize_with_readme = false
@auto_devops_enabled = false
@visibility = :public
@template_name = nil
+ @personal_namespace = nil
@import = false
self.name = "the_awesome_project"
@@ -68,7 +70,7 @@ module QA
def fabricate!
return if @import
- if @personal_namespace
+ if personal_namespace
Page::Dashboard::Projects.perform(&:click_new_project_button)
else
group.visit!
@@ -356,6 +358,46 @@ module QA
parse_body(response)
end
+ # Object comparison
+ #
+ # @param [QA::Resource::Project] other
+ # @return [Boolean]
+ def ==(other)
+ other.is_a?(Project) && comparable_project == other.comparable_project
+ end
+
+ # Override inspect for a better rspec failure diff output
+ #
+ # @return [String]
+ def inspect
+ JSON.pretty_generate(comparable_project)
+ end
+
+ protected
+
+ # Return subset of fields for comparing projects
+ #
+ # @return [Hash]
+ def comparable_project
+ reload! if api_response.nil?
+
+ api_resource.slice(
+ :name,
+ :path,
+ :description,
+ :tag_list,
+ :archived,
+ :issues_enabled,
+ :merge_request_enabled,
+ :wiki_enabled,
+ :jobs_enabled,
+ :snippets_enabled,
+ :shared_runners_enabled,
+ :request_access_enabled,
+ :avatar_url
+ )
+ end
+
private
def transform_api_resource(api_resource)
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index cffeed7a64b..28a0f12b3e3 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -34,7 +34,7 @@ module QA
def fabricate_via_api!
super
rescue ResourceURLMissingError
- "#{Runtime::Scenario.gitlab_address}/#{group.full_path}/#{name}"
+ "#{Runtime::Scenario.gitlab_address}/#{full_path}"
end
def api_post_path
@@ -49,7 +49,7 @@ module QA
{
repo_id: github_repo_id,
new_name: name,
- target_namespace: group.full_path,
+ target_namespace: @personal_namespace || group.full_path,
personal_access_token: github_personal_access_token,
ci_cd_only: false
}
diff --git a/qa/qa/resource/project_snippet.rb b/qa/qa/resource/project_snippet.rb
index 9ab4612d117..9a22966efdb 100644
--- a/qa/qa/resource/project_snippet.rb
+++ b/qa/qa/resource/project_snippet.rb
@@ -30,6 +30,8 @@ module QA
new_snippet.click_create_snippet_button
end
+
+ @id = Page::Project::Snippet::Show.perform(&:snippet_id)
end
def api_get_path
diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb
index a94ae02b8fd..a79e8c7de6b 100644
--- a/qa/qa/resource/snippet.rb
+++ b/qa/qa/resource/snippet.rb
@@ -22,6 +22,10 @@ module QA
end
def fabricate!
+ Page::Main::Menu.perform do |menu|
+ menu.go_to_menu_dropdown_option(:snippets_link)
+ end
+
Page::Dashboard::Snippet::Index.perform(&:go_to_new_snippet_page)
Page::Dashboard::Snippet::New.perform do |new_page|
@@ -38,6 +42,8 @@ module QA
end
new_page.click_create_snippet_button
end
+
+ @id = Page::Dashboard::Snippet::Show.perform(&:snippet_id)
end
def fabricate_via_api!
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index 811ce5e0505..ed4ea057484 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -7,13 +7,18 @@ module QA
attr_reader :unique_id
attr_writer :username, :password
- attr_accessor :admin, :provider, :extern_uid, :expect_fabrication_success, :hard_delete_on_api_removal
-
- attribute :id
- attribute :name
- attribute :first_name
- attribute :last_name
- attribute :email
+ attr_accessor :admin,
+ :provider,
+ :extern_uid,
+ :expect_fabrication_success,
+ :hard_delete_on_api_removal,
+ :access_level
+
+ attributes :id,
+ :name,
+ :first_name,
+ :last_name,
+ :email
def initialize
@admin = false
@@ -123,6 +128,10 @@ module QA
"/users/#{id}/block"
end
+ def api_approve_path
+ "/users/#{id}/approve"
+ end
+
def api_post_body
{
admin: admin,
@@ -148,6 +157,13 @@ module QA
end
end
+ def approve!
+ response = post(Runtime::API::Request.new(api_client, api_approve_path).url, nil)
+ return if response.code == 201
+
+ raise ResourceUpdateFailedError, "Failed to approve user. Request returned (#{response.code}): `#{response}`"
+ end
+
def block!
response = post(Runtime::API::Request.new(api_client, api_block_path).url, nil)
return if response.code == HTTP_STATUS_CREATED
diff --git a/qa/qa/runtime/allure_report.rb b/qa/qa/runtime/allure_report.rb
index 5b0456dc607..5f628050f3b 100644
--- a/qa/qa/runtime/allure_report.rb
+++ b/qa/qa/runtime/allure_report.rb
@@ -5,6 +5,8 @@ require 'active_support/core_ext/enumerable'
module QA
module Runtime
class AllureReport
+ extend QA::Support::API
+
class << self
# Configure allure reports
#
@@ -77,27 +79,21 @@ module QA
end
end
- # Custom environment info hash
+ # Gitlab version and revision information
#
# @return [Hash]
def environment_info
- %w[
- CI_COMMIT_SHA
- CI_MERGE_REQUEST_SOURCE_BRANCH_SHA
- CI_MERGE_REQUEST_IID
- TOP_UPSTREAM_SOURCE_SHA
- TOP_UPSTREAM_MERGE_REQUEST_IID
- DEPLOY_VERSION
- GITLAB_VERSION
- GITLAB_SHELL_VERSION
- GITLAB_ELASTICSEARCH_INDEXER_VERSION
- GITLAB_KAS_VERSION
- GITLAB_WORKHORSE_VERSION
- GITLAB_PAGES_VERSION
- GITALY_SERVER_VERSION
- QA_IMAGE
- QA_BROWSER
- ].index_with { |val| ENV[val] }.compact_blank
+ lambda do
+ return {} unless Env.admin_personal_access_token || Env.personal_access_token
+
+ client = Env.admin_personal_access_token ? API::Client.as_admin : API::Client.new
+ response = get(API::Request.new(client, '/version').url)
+
+ JSON.parse(response.body, symbolize_names: true)
+ rescue StandardError, ArgumentError => e
+ Logger.error("Failed to attach version info to allure report: #{e}")
+ {}
+ end
end
end
end
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 8a5e22fbc37..b5b572890c1 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -16,17 +16,21 @@ module QA
enable_ip_limits if ip_limits
end
+ # Personal access token
+ #
+ # It is possible to set the environment variable GITLAB_QA_ACCESS_TOKEN
+ # to use a specific access token rather than create one from the UI
+ # unless a specific user has been passed
+ #
+ # @return [String]
def personal_access_token
- @personal_access_token ||= begin
- # you can set the environment variable GITLAB_QA_ACCESS_TOKEN
- # to use a specific access token rather than create one from the UI
- # unless a specific user has been passed
- @user.nil? ? Runtime::Env.personal_access_token ||= create_personal_access_token : create_personal_access_token
- end
+ @personal_access_token ||= if user.nil?
+ Runtime::Env.personal_access_token ||= create_personal_access_token
+ else
+ create_personal_access_token
+ end
- if @user&.admin?
- Runtime::Env.admin_personal_access_token = @personal_access_token
- end
+ Runtime::Env.admin_personal_access_token = @personal_access_token if user&.admin? # rubocop:disable Cop/UserAdmin
@personal_access_token
end
@@ -82,27 +86,38 @@ module QA
Page::Main::Menu.perform(&:sign_out)
end
+ # Create PAT
+ #
+ # Use api if admin personal access token is present and skip any UI actions otherwise perform creation via UI
+ #
+ # @return [String]
def create_personal_access_token
- signed_in_initially = Page::Main::Menu.perform(&:signed_in?)
-
- Page::Main::Menu.perform(&:sign_out) if @is_new_session && signed_in_initially
-
- token = Resource::PersonalAccessToken.fabricate! do |pat|
- pat.user = user
- end.token
-
- # If this is a new session, that tests that follow could fail if they
- # try to sign in without starting a new session.
- # Also, if the browser wasn't already signed in, leaving it
- # signed in could cause tests to fail when they try to sign
- # in again. For example, that would happen if a test has a
- # before(:context) block that fabricates via the API, and
- # it's the first test to run so it creates an access token
- #
- # Sign out so the tests can successfully sign in
- Page::Main::Menu.perform(&:sign_out) if @is_new_session || !signed_in_initially
-
- token
+ if Runtime::Env.admin_personal_access_token
+ Resource::PersonalAccessToken.fabricate_via_api! do |pat|
+ pat.user = user
+ end.token
+ else
+ signed_in_initially = Page::Main::Menu.perform(&:signed_in?)
+
+ Page::Main::Menu.perform(&:sign_out) if @is_new_session && signed_in_initially
+
+ token = Resource::PersonalAccessToken.fabricate! do |pat|
+ pat.user = user
+ end.token
+
+ # If this is a new session, that tests that follow could fail if they
+ # try to sign in without starting a new session.
+ # Also, if the browser wasn't already signed in, leaving it
+ # signed in could cause tests to fail when they try to sign
+ # in again. For example, that would happen if a test has a
+ # before(:context) block that fabricates via the API, and
+ # it's the first test to run so it creates an access token
+ #
+ # Sign out so the tests can successfully sign in
+ Page::Main::Menu.perform(&:sign_out) if @is_new_session || !signed_in_initially
+
+ token
+ end
end
end
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index 5adc52680f0..71e3383a534 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -46,6 +46,10 @@ module QA
end
end
+ def stop_primary_node
+ stop_node(@primary_node)
+ end
+
def start_primary_node
start_node(@primary_node)
end
@@ -66,20 +70,29 @@ module QA
start_node(@secondary_node)
end
+ def stop_tertiary_node
+ stop_node(@tertiary_node)
+ end
+
+ def start_tertiary_node
+ start_node(@tertiary_node)
+ end
+
def start_node(name)
shell "docker start #{name}"
+ wait_until_shell_command_matches(
+ "docker inspect -f {{.State.Running}} #{name}",
+ /true/,
+ sleep_interval: 3,
+ max_duration: 180,
+ retry_on_exception: true
+ )
end
def stop_node(name)
shell "docker stop #{name}"
end
- def trigger_failover_by_stopping_primary_node
- QA::Runtime::Logger.info("Stopping node #{@primary_node} to trigger failover")
- stop_node(@primary_node)
- wait_for_new_primary
- end
-
def clear_replication_queue
QA::Runtime::Logger.info("Clearing the replication queue")
shell sql_to_docker_exec_cmd(
@@ -157,22 +170,8 @@ module QA
result[2].to_i
end
- # Makes the original primary (gitaly1) the primary again by
- # stopping the other nodes, waiting for gitaly1 to be made the
- # primary again, and then it starts the other nodes and enables
- # writes
- def reset_primary_to_original
- QA::Runtime::Logger.info("Checking primary node...")
-
- return if @primary_node == current_primary_node
-
- QA::Runtime::Logger.info("Reset primary node to #{@primary_node}")
+ def start_all_nodes
start_node(@primary_node)
- stop_node(@secondary_node)
- stop_node(@tertiary_node)
-
- wait_for_new_primary_node(@primary_node)
-
start_node(@secondary_node)
start_node(@tertiary_node)
@@ -189,10 +188,12 @@ module QA
end
def wait_for_praefect
- QA::Runtime::Logger.info('Wait until Praefect starts and is listening')
wait_until_shell_command_matches(
- "docker exec #{@praefect} bash -c 'cat /var/log/gitlab/praefect/current'",
- /listening at tcp address/
+ "docker inspect -f {{.State.Running}} #{@praefect}",
+ /true/,
+ sleep_interval: 3,
+ max_duration: 180,
+ retry_on_exception: true
)
# Praefect can fail to start if unable to dial one of the gitaly nodes
@@ -204,20 +205,6 @@ module QA
end
end
- def wait_for_new_primary_node(node)
- QA::Runtime::Logger.info("Wait until #{node} is the primary node")
- with_praefect_log(max_duration: 120) do |log|
- break true if log['msg'] == 'primary node changed' && log['newPrimary'] == node
- end
- end
-
- def wait_for_new_primary
- QA::Runtime::Logger.info("Wait until a new primary node is selected")
- with_praefect_log(max_duration: 120) do |log|
- break true if log['msg'] == 'primary node changed'
- end
- end
-
def wait_for_sql_ping
wait_until_shell_command_matches(
"docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'",
@@ -274,10 +261,6 @@ module QA
end
end
- def wait_for_health_check_current_primary_node
- wait_for_health_check(current_primary_node)
- end
-
def wait_for_health_check_all_nodes
wait_for_health_check(@primary_node)
wait_for_health_check(@secondary_node)
@@ -286,29 +269,58 @@ module QA
def wait_for_health_check(node)
QA::Runtime::Logger.info("Waiting for health check on #{node}")
- wait_until_shell_command("docker exec #{node} bash -c 'cat /var/log/gitlab/gitaly/current'") do |line|
- QA::Runtime::Logger.debug(line.chomp)
- log = JSON.parse(line)
+ wait_until_node_is_marked_as_healthy_storage(node)
+ end
- log['grpc.request.fullMethod'] == '/grpc.health.v1.Health/Check' && log['grpc.code'] == 'OK'
- rescue JSON::ParserError
- # Ignore lines that can't be parsed as JSON
- end
+ def wait_for_primary_node_health_check
+ wait_for_health_check(@primary_node)
+ end
+
+ def wait_for_secondary_node_health_check
+ wait_for_health_check(@secondary_node)
+ end
+
+ def wait_for_tertiary_node_health_check
+ wait_for_health_check(@tertiary_node)
+ end
+
+ def wait_for_health_check_failure(node)
+ QA::Runtime::Logger.info("Waiting for health check failure on #{node}")
+ wait_until_node_is_removed_from_healthy_storages(node)
+ end
+
+ def wait_for_primary_node_health_check_failure
+ wait_for_health_check_failure(@primary_node)
end
def wait_for_secondary_node_health_check_failure
wait_for_health_check_failure(@secondary_node)
end
- def wait_for_health_check_failure(node)
- QA::Runtime::Logger.info("Waiting for Praefect to record a health check failure on #{node}")
- wait_until_shell_command("docker exec #{@praefect} bash -c 'tail -n 1 /var/log/gitlab/praefect/current'") do |line|
- QA::Runtime::Logger.debug(line.chomp)
- log = JSON.parse(line)
+ def wait_for_tertiary_node_health_check_failure
+ wait_for_health_check_failure(@tertiary_node)
+ end
- health_check_failure_message?(log['msg']) && log['storage'] == node
- rescue JSON::ParserError
- # Ignore lines that can't be parsed as JSON
+ def wait_until_node_is_removed_from_healthy_storages(node)
+ Support::Waiter.wait_until(max_duration: 60, sleep_interval: 3, raise_on_failure: false) do
+ result = []
+ shell sql_to_docker_exec_cmd("SELECT count(*) FROM healthy_storages WHERE storage = '#{node}';") do |line|
+ result << line
+ end
+ QA::Runtime::Logger.debug("result is ---#{result}")
+ result[2].to_i == 0
+ end
+ end
+
+ def wait_until_node_is_marked_as_healthy_storage(node)
+ Support::Waiter.wait_until(max_duration: 60, sleep_interval: 3, raise_on_failure: false) do
+ result = []
+ shell sql_to_docker_exec_cmd("SELECT count(*) FROM healthy_storages WHERE storage = '#{node}';") do |line|
+ result << line
+ end
+
+ QA::Runtime::Logger.debug("result is ---#{result}")
+ result[2].to_i == 1
end
end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 81cfaa125a9..5a35d8c251e 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -52,7 +52,7 @@ module QA
end
def wait_until_shell_command_matches(cmd, regex, **kwargs)
- wait_until_shell_command(cmd, kwargs) do |line|
+ wait_until_shell_command(cmd, **kwargs) do |line|
QA::Runtime::Logger.debug(line.chomp)
line =~ regex
diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb
index 1422dd5a029..8b4900957c5 100644
--- a/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb
@@ -89,7 +89,7 @@ module QA
end
end
- context 'with milestones' do
+ context 'with milestones and badges' do
let(:source_milestone) do
Resource::GroupMilestone.fabricate_via_api! do |milestone|
milestone.api_client = api_client
@@ -99,10 +99,17 @@ module QA
before do
source_milestone
+
+ Resource::GroupBadge.fabricate_via_api! do |badge|
+ badge.api_client = api_client
+ badge.group = source_group
+ badge.link_url = "http://example.com/badge"
+ badge.image_url = "http://shields.io/badge"
+ end
end
it(
- 'successfully imports group milestones',
+ 'successfully imports group milestones and badges',
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/2245'
) do
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
@@ -113,8 +120,40 @@ module QA
expect(imported_milestone.iid).to eq(source_milestone.iid)
expect(imported_milestone.created_at).to eq(source_milestone.created_at)
expect(imported_milestone.updated_at).to eq(source_milestone.updated_at)
+
+ expect(imported_group.badges).to eq(source_group.badges)
+ end
+ end
+ end
+
+ context 'with group members' do
+ let(:member) do
+ Resource::User.fabricate_via_api! do |usr|
+ usr.api_client = admin_api_client
+ usr.hard_delete_on_api_removal = true
end
end
+
+ before do
+ member.set_public_email
+ source_group.add_member(member, Resource::Members::AccessLevel::DEVELOPER)
+ end
+
+ after do
+ member.remove_via_api!
+ end
+
+ it(
+ 'adds members for imported group',
+ testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/2310'
+ ) do
+ expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
+
+ imported_member = imported_group.reload!.members.find { |usr| usr.username == member.username }
+
+ expect(imported_member).not_to be_nil
+ expect(imported_member.access_level).to eq(Resource::Members::AccessLevel::DEVELOPER)
+ end
end
after do
diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb
new file mode 100644
index 00000000000..9935908d55e
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage', :requires_admin do
+ describe 'Bulk project import' do
+ let!(:staging?) { Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') }
+
+ let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+ let(:user) do
+ Resource::User.fabricate_via_api! do |usr|
+ usr.api_client = admin_api_client
+ usr.hard_delete_on_api_removal = true
+ end
+ end
+
+ let(:api_client) { Runtime::API::Client.new(user: user) }
+
+ let(:sandbox) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = admin_api_client
+ end
+ end
+
+ let(:source_group) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
+ end
+ end
+
+ let(:source_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.api_client = api_client
+ project.group = source_group
+ end
+ end
+
+ let(:imported_group) do
+ Resource::BulkImportGroup.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.sandbox = sandbox
+ group.source_group_path = source_group.path
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(:bulk_import_projects)
+ Runtime::Feature.enable(:top_level_group_creation_enabled) if staging?
+
+ sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+
+ source_project # fabricate source group and project
+ end
+
+ after do
+ user.remove_via_api!
+ ensure
+ Runtime::Feature.disable(:bulk_import_projects)
+ Runtime::Feature.disable(:top_level_group_creation_enabled) if staging?
+ end
+
+ context 'with project' do
+ it(
+ 'successfully imports project',
+ testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/2297'
+ ) do
+ expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
+
+ imported_projects = imported_group.reload!.projects
+ aggregate_failures do
+ expect(imported_projects.count).to eq(1)
+ expect(imported_projects.first).to eq(source_project)
+ end
+ end
+ end
+ end
+ end
+end
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 b51a79f239c..3a2f960d812 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
@@ -9,11 +9,6 @@ module QA
let(:differ) { RSpec::Support::Differ.new(color: true) }
let(:api_client) { Runtime::API::Client.as_admin }
- let(:group) do
- Resource::Group.fabricate_via_api! do |resource|
- resource.api_client = api_client
- end
- end
let(:user) do
Resource::User.fabricate_via_api! do |resource|
@@ -86,19 +81,15 @@ module QA
Resource::ProjectImportedFromGithub.fabricate_via_api! do |project|
project.add_name_uuid = false
project.name = 'imported-project'
- project.group = group
project.github_personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = github_repo
+ project.personal_namespace = user.username
project.api_client = api_client
end
end
- before do
- group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
- end
-
after do |example|
- user.remove_via_api!
+ user.remove_via_api! unless example.exception
next unless defined?(@import_time)
# save data for comparison after run finished
@@ -128,7 +119,10 @@ module QA
)
end
- it 'imports large Github repo via api', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1880' do
+ it(
+ 'imports large Github repo via api',
+ testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1880'
+ ) do
start = Time.now
Runtime::Logger.info("Importing project '#{imported_project.full_path}'") # import the project and log path
diff --git a/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
index 19fdb37f788..ec4f0387128 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
@@ -14,7 +14,7 @@ module QA
before(:context) do
# Reset the cluster in case previous tests left it in a bad state
- praefect_manager.reset_primary_to_original
+ praefect_manager.start_all_nodes
project = Resource::Project.fabricate! do |project|
project.name = "gitaly_cluster"
@@ -25,25 +25,35 @@ module QA
after(:context, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/238187', type: :stale }) do
# Leave the cluster in a suitable state for subsequent tests,
# if there was a problem during the tests here
- praefect_manager.reset_primary_to_original
+ praefect_manager.start_all_nodes
end
it 'automatically fails over', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1267' do
# Create a new project with a commit and wait for it to replicate
+
+ # make sure that our project is published to the 'primary' node
+ praefect_manager.stop_secondary_node
+ praefect_manager.stop_tertiary_node
+ praefect_manager.wait_for_secondary_node_health_check_failure
+ praefect_manager.wait_for_tertiary_node_health_check_failure
+
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = first_added_commit_message
push.new_branch = false
- push.file_content = "This should exist on both nodes"
+ push.file_content = "This should exist on all nodes"
end
+ praefect_manager.start_secondary_node
+ praefect_manager.start_tertiary_node
+ praefect_manager.wait_for_health_check_all_nodes
+
praefect_manager.wait_for_replication(project.id)
# Stop the primary node to trigger failover, and then wait
# for Gitaly to be ready for writes again
- praefect_manager.trigger_failover_by_stopping_primary_node
- praefect_manager.wait_for_new_primary
- praefect_manager.wait_for_health_check_current_primary_node
+ praefect_manager.stop_primary_node
+ praefect_manager.wait_for_primary_node_health_check_failure
praefect_manager.wait_for_gitaly_check
Resource::Repository::Commit.fabricate_via_api! do |commit|
@@ -69,7 +79,7 @@ module QA
it 'automatically reconciles', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/238187', type: :stale }, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1266' do
# Start the old primary node again
praefect_manager.start_primary_node
- praefect_manager.wait_for_health_check_current_primary_node
+ praefect_manager.wait_for_primary_node_health_check
# Confirm automatic reconciliation
expect(praefect_manager.replicated?(project.id)).to be true
@@ -81,7 +91,7 @@ module QA
.and include(second_added_commit_message)
# Restore the original primary node
- praefect_manager.reset_primary_to_original
+ praefect_manager.start_all_nodes
# Check that all commits are still available even though the primary
# node was offline when one was made
diff --git a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb
index f00321ee3f5..37670b70fd8 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb
@@ -14,12 +14,12 @@ module QA
before do
# Reset the cluster in case previous tests left it in a bad state
- praefect_manager.reset_primary_to_original
+ praefect_manager.start_all_nodes
end
after do
# Leave the cluster in a suitable state for subsequent tests
- praefect_manager.reset_primary_to_original
+ praefect_manager.start_all_nodes
end
it 'recovers from dataloss', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1265' do
@@ -28,9 +28,7 @@ module QA
# Stop the primary node to trigger failover, and then wait
# for Gitaly to be ready for writes again
- praefect_manager.trigger_failover_by_stopping_primary_node
- praefect_manager.wait_for_new_primary
- praefect_manager.wait_for_health_check_current_primary_node
+ praefect_manager.stop_primary_node
praefect_manager.wait_for_gitaly_check
# Confirm that we have access to the repo after failover
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index caaa615149d..db75d4639ff 100644
--- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -46,10 +46,9 @@ module QA
def create_project(user, api_client, project_name)
project = Resource::Project.fabricate_via_api! do |project|
- project.personal_namespace = true
+ project.personal_namespace = user.username
project.add_name_uuid = false
project.name = project_name
- project.path_with_namespace = "#{user.username}/#{project_name}"
project.api_client = api_client
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
index c3be58fda74..c136d14c1e5 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb
@@ -58,8 +58,8 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1806',
issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252',
issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678',
- # mostly impacts testing as it makes small groups import slower
- issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351'
+ issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351',
+ except: { job: 'instance-image-slow-network' }
) do
Page::Group::BulkImport.perform do |import_page|
import_page.import_group(imported_group.path, imported_group.sandbox.path)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
index a3235543998..4e15aa79623 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_recovery_spec.rb
@@ -78,6 +78,7 @@ module QA
@otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content)
two_fa_auth.set_pin_code(@otp.fresh_otp)
+ two_fa_auth.set_current_password(user.password)
two_fa_auth.click_register_2fa_app_button
recovery_code = two_fa_auth.recovery_codes.sample
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 8a2bbc92eca..b97da194795 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
@@ -55,6 +55,7 @@ module QA
Page::Profile::TwoFactorAuth.perform do |two_fa_auth|
otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content)
two_fa_auth.set_pin_code(otp.fresh_otp)
+ two_fa_auth.set_current_password(user.password)
two_fa_auth.click_register_2fa_app_button
two_fa_auth.click_copy_and_proceed
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
index 2fe1cbabee3..78fbec594a3 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb
@@ -98,6 +98,7 @@ module QA
@otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content)
two_fa_auth.set_pin_code(@otp.fresh_otp)
+ two_fa_auth.set_current_password(user.password)
two_fa_auth.click_register_2fa_app_button
two_fa_auth.click_copy_and_proceed
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 974d9b02f4d..af4e7126c29 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :smoke do
- describe 'Project' do
+ describe 'Project', :requires_admin do
shared_examples 'successful project creation' do
it 'creates a new project' do
Page::Project::Show.perform do |project|
@@ -17,6 +17,7 @@ module QA
end
before do
+ Runtime::Feature.enable(:paginatable_namespace_drop_down_for_project_creation)
Flow::Login.sign_in
project
end
@@ -39,7 +40,7 @@ module QA
Resource::Project.fabricate_via_browser_ui! do |project|
project.name = project_name
project.description = 'create awesome project test'
- project.personal_namespace = true
+ project.personal_namespace = Runtime::User.username
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 4090837d5c9..f2d4fc6e677 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -1,22 +1,29 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332588', type: :investigating } do
+ RSpec.describe 'Create' do
describe 'Merge request creation from fork' do
- # TODO: Please add this back to :smoke suite as soon as https://gitlab.com/gitlab-org/gitlab/-/issues/332588 is addressed
- it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1305' do
- merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
+ let(:merge_request) do
+ Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
- Flow::Login.while_signed_in do
- merge_request.visit!
+ after do
+ merge_request.fork.remove_via_api!
+ end
+
+ it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1305' do
+ merge_request.visit!
- Page::MergeRequest::Show.perform do |merge_request|
- merge_request.merge!
+ Page::MergeRequest::Show.perform do |merge_request|
+ merge_request.merge!
- expect(merge_request).to have_content('The changes were merged')
- end
+ expect(merge_request).to be_merged
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
index 0ea294b8e51..6a79a2d8078 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
@@ -1,11 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', quarantine: {
- only: { job: 'large-setup' },
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338324',
- type: :stale
- } do
+ RSpec.describe 'Create' do
describe 'Merged merge request' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -23,7 +19,7 @@ module QA
Flow::Login.sign_in
end
- it 'can be reverted', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1818' do
+ it 'can be reverted', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1818', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/335987', type: :investigating } do
revertable_merge_request.visit!
Page::MergeRequest::Show.perform do |merge_request|
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
index 5cebbb32ade..1752513a831 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
@@ -46,11 +46,11 @@ module QA
merge_request.visit!
end
- it 'applies multiple suggestions', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1838' do
+ it 'applies multiple suggestions', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1838', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/342131', type: :stale } do
Page::MergeRequest::Show.perform do |merge_request|
merge_request.click_diffs_tab
4.times { merge_request.add_suggestion_to_batch }
- merge_request.apply_suggestions_batch
+ merge_request.apply_suggestion_with_message("Custom commit message")
expect(merge_request).to have_css('.badge-success', text: "Applied", count: 4)
end
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 1080d8ab849..300fd6a1be2 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
@@ -3,13 +3,7 @@
module QA
RSpec.describe 'Create' do # convert back to a smoke test once proved to be stable
describe 'Personal snippet creation' do
- it 'user creates a personal snippet', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1357' do
- Flow::Login.sign_in
-
- Page::Main::Menu.perform do |menu|
- menu.go_to_menu_dropdown_option(:snippets_link)
- end
-
+ let(:snippet) do
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Snippet title'
snippet.description = 'Snippet description'
@@ -17,6 +11,18 @@ module QA
snippet.file_name = 'ruby_file.rb'
snippet.file_content = 'File.read("test.txt").split(/\n/)'
end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ snippet.remove_via_api!
+ end
+
+ it 'user creates a personal snippet', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1357' do
+ snippet.visit!
Page::Dashboard::Snippet::Show.perform do |snippet|
expect(snippet).to have_snippet_title('Snippet title')
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
index 525fc5799a9..a32ee472150 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
@@ -3,17 +3,11 @@
module QA
RSpec.describe 'Create' do
describe 'Multiple file snippet' do
- it 'creates a personal snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1654' do
- Flow::Login.sign_in
-
- Page::Main::Menu.perform do |menu|
- menu.go_to_menu_dropdown_option(:snippets_link)
- end
-
+ let(:snippet) do
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Personal snippet with multiple files'
snippet.description = 'Snippet description'
- snippet.visibility = 'Public'
+ snippet.visibility = 'Private'
snippet.file_name = 'First file name'
snippet.file_content = 'First file content'
@@ -22,11 +16,23 @@ module QA
files.append(name: 'Third file name', content: 'Third file content')
end
end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ snippet.remove_via_api!
+ end
+
+ it 'creates a personal snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1654' do
+ snippet.visit!
Page::Dashboard::Snippet::Show.perform do |snippet|
expect(snippet).to have_snippet_title('Personal snippet with multiple files')
expect(snippet).to have_snippet_description('Snippet description')
- expect(snippet).to have_visibility_type(/public/i)
+ expect(snippet).to have_visibility_type(/private/i)
expect(snippet).to have_file_name('First file name', 1)
expect(snippet).to have_file_content('First file content', 1)
expect(snippet).to have_file_name('Second file name', 2)
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
index e4c2488e8df..1967273ca17 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
@@ -3,9 +3,7 @@
module QA
RSpec.describe 'Create' do # to be converted to a smoke test once proved to be stable
describe 'Project snippet creation' do
- it 'user creates a project snippet', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1358' do
- Flow::Login.sign_in
-
+ let(:snippet) do
Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Project snippet'
snippet.description = ' '
@@ -13,6 +11,18 @@ module QA
snippet.file_name = 'markdown_file.md'
snippet.file_content = "### Snippet heading\n\n[Gitlab link](https://gitlab.com/)"
end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ snippet.remove_via_api!
+ end
+
+ it 'user creates a project snippet', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1358' do
+ snippet.visit!
Page::Dashboard::Snippet::Show.perform do |snippet|
expect(snippet).to have_snippet_title('Project snippet')
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
index 3298989cc12..ae71be26a38 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
@@ -3,9 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Multiple file snippet' do
- it 'creates a project snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1648' do
- Flow::Login.sign_in
-
+ let(:snippet) do
Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Project snippet with multiple files'
snippet.description = 'Snippet description'
@@ -20,6 +18,18 @@ module QA
end
end
end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ snippet.remove_via_api!
+ end
+
+ it 'creates a project snippet with multiple files', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1648' do
+ snippet.visit!
Page::Dashboard::Snippet::Show.perform do |snippet|
aggregate_failures 'file content verification' do
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb
new file mode 100644
index 00000000000..51791c01048
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe(
+ 'Create',
+ :runner,
+ quarantine: {
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338179',
+ type: :bug
+ }
+ ) do
+ describe 'Web IDE web terminal' do
+ before do
+ project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'web-terminal-project'
+ end
+
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab/.gitlab-webide.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab/.gitlab-webide.yml',
+ content: <<~YAML
+ terminal:
+ tags: ["web-ide"]
+ script: sleep 60
+ YAML
+ }
+ ]
+ )
+ end
+
+ @runner = Resource::Runner.fabricate_via_api! do |runner|
+ runner.project = project
+ runner.name = "qa-runner-#{Time.now.to_i}"
+ runner.tags = %w[web-ide]
+ runner.image = 'gitlab/gitlab-runner:latest'
+ runner.config = <<~END
+ concurrent = 1
+
+ [session_server]
+ listen_address = "0.0.0.0:8093"
+ advertise_address = "localhost:8093"
+ session_timeout = 120
+ END
+ end
+
+ Flow::Login.sign_in
+
+ project.visit!
+ end
+
+ after do
+ @runner.remove_via_api! if @runner
+ end
+
+ it 'user starts the web terminal', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1593' do
+ Page::Project::Show.perform(&:open_web_ide!)
+
+ # Start the web terminal and check that there were no errors
+ # The terminal screen is a canvas element, so we can't read its content,
+ # so we infer that it's working if:
+ # a) The terminal JS package has loaded, and
+ # b) It's not stuck in a "Loading/Starting" state, and
+ # c) There's no alert stating there was a problem
+ #
+ # The terminal itself is a third-party package so we assume it is
+ # adequately tested elsewhere.
+ #
+ # There are also FE specs
+ # * spec/frontend/ide/components/terminal/terminal_controls_spec.js
+ Page::Project::WebIDE::Edit.perform do |edit|
+ edit.start_web_terminal
+
+ expect(edit).to have_no_alert
+ expect(edit).to have_finished_loading
+ expect(edit).to have_terminal_screen
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb
index bfcc49885a0..ea7f7cc1c05 100644
--- a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy_spec.rb
@@ -28,9 +28,9 @@ module QA
project.group.visit!
- Page::Group::Menu.perform(&:go_to_dependency_proxy)
+ Page::Group::Menu.perform(&:go_to_package_settings)
- Page::Group::DependencyProxy.perform do |index|
+ Page::Group::Settings::PackageRegistries.perform do |index|
expect(index).to have_dependency_proxy_enabled
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/helm_registry_spec.rb
new file mode 100644
index 00000000000..fe52fd03ad8
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/5_package/helm_registry_spec.rb
@@ -0,0 +1,150 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
+ describe 'Helm Registry' do
+ include Runtime::Fixtures
+ include_context 'packages registry qa scenario'
+
+ let(:package_name) { 'gitlab_qa_helm' }
+ let(:package_version) { '1.3.7' }
+ let(:package_type) { 'helm' }
+
+ let(:package_gitlab_ci_file) do
+ {
+ file_path: '.gitlab-ci.yml',
+ content:
+ <<~YAML
+ deploy:
+ image: alpine:3
+ script:
+ - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
+ - apk add curl
+ - helm create #{package_name}
+ - cp ./Chart.yaml #{package_name}
+ - helm package #{package_name}
+ - http_code=$(curl --write-out "%{http_code}" --request POST --form 'chart=@#{package_name}-#{package_version}.tgz' --user #{username}:#{access_token} ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts --output /dev/null --silent)
+ - '[ $http_code = "201" ]'
+ only:
+ - "#{package_project.default_branch}"
+ tags:
+ - "runner-for-#{package_project.group.name}"
+ YAML
+ }
+ end
+
+ let(:package_chart_yaml_file) do
+ {
+ file_path: "Chart.yaml",
+ content:
+ <<~EOF
+ apiVersion: v2
+ name: #{package_name}
+ description: GitLab QA helm package
+ type: application
+ version: #{package_version}
+ appVersion: "1.16.0"
+ EOF
+ }
+ end
+
+ let(:client_gitlab_ci_file) do
+ {
+ file_path: '.gitlab-ci.yml',
+ content:
+ <<~YAML
+ pull:
+ image: alpine:3
+ script:
+ - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
+ - helm repo add --username #{username} --password #{access_token} gitlab_qa ${CI_API_V4_URL}/projects/#{package_project.id}/packages/helm/stable
+ - helm repo update
+ - helm pull gitlab_qa/#{package_name}
+ only:
+ - "#{client_project.default_branch}"
+ tags:
+ - "runner-for-#{client_project.group.name}"
+ YAML
+ }
+ end
+
+ %i[personal_access_token ci_job_token project_deploy_token].each do |authentication_token_type|
+ context "using a #{authentication_token_type}" do
+ let(:username) do
+ case authentication_token_type
+ when :personal_access_token
+ Runtime::User.username
+ when :ci_job_token
+ 'gitlab-ci-token'
+ when :project_deploy_token
+ project_deploy_token.username
+ end
+ end
+
+ let(:access_token) do
+ case authentication_token_type
+ when :personal_access_token
+ personal_access_token
+ when :ci_job_token
+ '${CI_JOB_TOKEN}'
+ when :project_deploy_token
+ project_deploy_token.password
+ end
+ end
+
+ it "pushes and pulls a helm chart" do
+ # pushing
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = package_project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files([package_gitlab_ci_file, package_chart_yaml_file])
+ end
+
+ package_project.visit!
+
+ Flow::Pipeline.visit_latest_pipeline
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('deploy')
+ end
+
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
+
+ Page::Project::Menu.perform(&:click_packages_link)
+
+ Page::Project::Packages::Index.perform do |index|
+ expect(index).to have_package(package_name)
+
+ index.click_package(package_name)
+ end
+
+ Page::Project::Packages::Show.perform do |show|
+ expect(show).to have_package_info(package_name, package_version)
+ end
+
+ # pulling
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = client_project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files([client_gitlab_ci_file])
+ end
+
+ client_project.visit!
+
+ Flow::Pipeline.visit_latest_pipeline
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('pull')
+ end
+
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb
index fb3f2abd87a..ec9feca84b9 100644
--- a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb
@@ -5,57 +5,13 @@ module QA
describe 'Maven Repository with Gradle' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+ include_context 'packages registry qa scenario'
let(:group_id) { 'com.gitlab.qa' }
let(:artifact_id) { 'maven_gradle' }
let(:package_name) { "#{group_id}/#{artifact_id}".tr('.', '/') }
let(:package_version) { '1.3.7' }
-
- let(:personal_access_token) { Runtime::Env.personal_access_token }
-
- let(:package_project) do
- Resource::Project.fabricate_via_api! do |project|
- project.name = 'maven-with-gradle-project'
- project.initialize_with_readme = true
- project.visibility = :private
- end
- end
-
- let(:client_project) do
- Resource::Project.fabricate_via_api! do |client_project|
- client_project.name = 'gradle_client'
- client_project.initialize_with_readme = true
- client_project.group = package_project.group
- end
- end
-
- let(:package) do
- Resource::Package.init do |package|
- package.name = package_name
- package.project = package_project
- end
- end
-
- let(:runner) do
- Resource::Runner.fabricate! do |runner|
- runner.name = "qa-runner-#{Time.now.to_i}"
- runner.tags = ["runner-for-#{package_project.group.name}"]
- runner.executor = :docker
- runner.token = package_project.group.runners_token
- end
- end
-
- let(:gitlab_address_with_port) do
- uri = URI.parse(Runtime::Scenario.gitlab_address)
- "#{uri.scheme}://#{uri.host}:#{uri.port}"
- end
-
- let(:project_deploy_token) do
- Resource::DeployToken.fabricate_via_browser_ui! do |deploy_token|
- deploy_token.name = 'maven-with-gradle-deploy-token'
- deploy_token.project = package_project
- end
- end
+ let(:package_type) { 'maven_gradle' }
let(:package_gitlab_ci_file) do
{
@@ -127,18 +83,6 @@ module QA
}
end
- before do
- Flow::Login.sign_in_unless_signed_in
- runner
- end
-
- after do
- runner.remove_via_api!
- package.remove_via_api!
- package_project.remove_via_api!
- client_project.remove_via_api!
- end
-
where(:authentication_token_type, :maven_header_name) do
:personal_access_token | 'Private-Token'
:ci_job_token | 'Job-Token'
diff --git a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
index ba41285ebca..b928eae62e6 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Configure', except: { job: 'review-qa-*' } do
- describe 'Kubernetes Cluster Integration', :requires_admin, :skip_live_env, :smoke do
+ describe 'Kubernetes Cluster Integration', :orchestrated, :requires_admin, :skip_live_env do
context 'Project Clusters' do
let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 205ddf7ad3a..663761805ee 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -8,63 +8,93 @@ module QA
HTTP_STATUS_NO_CONTENT = 204
HTTP_STATUS_ACCEPTED = 202
HTTP_STATUS_NOT_FOUND = 404
+ HTTP_STATUS_TOO_MANY_REQUESTS = 429
HTTP_STATUS_SERVER_ERROR = 500
def post(url, payload, args = {})
- default_args = {
- method: :post,
- url: url,
- payload: payload,
- verify_ssl: false
- }
-
- RestClient::Request.execute(
- default_args.merge(args)
- )
- rescue RestClient::ExceptionWithResponse => e
- return_response_or_raise(e)
+ with_retry_on_too_many_requests do
+ default_args = {
+ method: :post,
+ url: url,
+ payload: payload,
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(
+ default_args.merge(args)
+ )
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
end
def get(url, args = {})
- default_args = {
- method: :get,
- url: url,
- verify_ssl: false
- }
-
- RestClient::Request.execute(
- default_args.merge(args)
- )
- rescue RestClient::ExceptionWithResponse => e
- return_response_or_raise(e)
+ with_retry_on_too_many_requests do
+ default_args = {
+ method: :get,
+ url: url,
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(
+ default_args.merge(args)
+ )
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
end
def put(url, payload = nil)
- RestClient::Request.execute(
- method: :put,
- url: url,
- payload: payload,
- verify_ssl: false)
- rescue RestClient::ExceptionWithResponse => e
- return_response_or_raise(e)
+ with_retry_on_too_many_requests do
+ RestClient::Request.execute(
+ method: :put,
+ url: url,
+ payload: payload,
+ verify_ssl: false)
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
end
def delete(url)
- RestClient::Request.execute(
- method: :delete,
- url: url,
- verify_ssl: false)
- rescue RestClient::ExceptionWithResponse => e
- return_response_or_raise(e)
+ with_retry_on_too_many_requests do
+ RestClient::Request.execute(
+ method: :delete,
+ url: url,
+ verify_ssl: false)
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
end
def head(url)
- RestClient::Request.execute(
- method: :head,
- url: url,
- verify_ssl: false)
- rescue RestClient::ExceptionWithResponse => e
- return_response_or_raise(e)
+ with_retry_on_too_many_requests do
+ RestClient::Request.execute(
+ method: :head,
+ url: url,
+ verify_ssl: false)
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
+ end
+
+ def with_retry_on_too_many_requests
+ response = nil
+
+ Support::Retrier.retry_until(log: false) do
+ response = yield
+
+ if response.code == HTTP_STATUS_TOO_MANY_REQUESTS
+ wait_seconds = response.headers[:retry_after].to_i
+ QA::Runtime::Logger.debug("Received 429 - Too many requests. Waiting for #{wait_seconds} seconds.")
+
+ sleep wait_seconds
+ end
+
+ response.code != HTTP_STATUS_TOO_MANY_REQUESTS
+ end
+
+ response
end
def parse_body(response)
diff --git a/qa/qa/support/formatters/test_stats_formatter.rb b/qa/qa/support/formatters/test_stats_formatter.rb
index 0f76a924b10..0484bd7f90f 100644
--- a/qa/qa/support/formatters/test_stats_formatter.rb
+++ b/qa/qa/support/formatters/test_stats_formatter.rb
@@ -32,7 +32,6 @@ module QA
influxdb_token,
bucket: 'e2e-test-stats',
org: 'gitlab-qa',
- use_ssl: false,
precision: InfluxDB2::WritePrecision::NANOSECOND
)
end
@@ -57,19 +56,22 @@ module QA
# @param [RSpec::Core::Example] example
# @return [Hash]
def test_stats(example)
+ file_path = example.metadata[:file_path].gsub('./qa/specs/features', '')
+
{
name: 'test-stats',
time: time,
tags: {
name: example.full_description,
- file_path: example.metadata[:file_path].gsub('./qa/specs/features', ''),
+ file_path: file_path,
status: example.execution_result.status,
reliable: example.metadata.key?(:reliable).to_s,
quarantined: example.metadata.key?(:quarantine).to_s,
retried: ((example.metadata[:retry_attempts] || 0) > 0).to_s,
job_name: job_name,
merge_request: merge_request,
- run_type: env('QA_RUN_TYPE') || run_type
+ run_type: env('QA_RUN_TYPE') || run_type,
+ stage: devops_stage(file_path)
},
fields: {
id: example.id,
@@ -113,11 +115,11 @@ module QA
@merge_request ||= (!!env('CI_MERGE_REQUEST_IID') || !!env('TOP_UPSTREAM_MERGE_REQUEST_IID')).to_s
end
- # Test run type from staging, canary or production env
+ # Test run type from staging, canary, preprod or production env
#
- # @return [String>, nil]
+ # @return [String, nil]
def run_type
- return unless %w[staging canary production].include?(project_name)
+ return unless %w[staging canary preprod production].include?(project_name)
@run_type ||= begin
test_subset = if env('NO_ADMIN') == 'true'
@@ -150,6 +152,14 @@ module QA
ENV[name]
end
+
+ # Get spec devops stage
+ #
+ # @param [String] location
+ # @return [String, nil]
+ def devops_stage(file_path)
+ file_path.match(%r{(\d{1,2}_\w+)/})&.captures&.first
+ end
end
end
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
index fde8ac263ca..aa568d633fc 100644
--- a/qa/qa/support/retrier.rb
+++ b/qa/qa/support/retrier.rb
@@ -34,15 +34,29 @@ module QA
result
end
- def retry_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
+ def retry_until(
+ max_attempts: nil,
+ max_duration: nil,
+ reload_page: nil,
+ sleep_interval: 0,
+ raise_on_failure: true,
+ retry_on_exception: false,
+ log: true
+ )
# For backwards-compatibility
max_attempts = 3 if max_attempts.nil? && max_duration.nil?
if log
- start_msg ||= ["with retry_until:"]
+ start_msg = ["with retry_until:"]
start_msg << "max_attempts: #{max_attempts};" if max_attempts
start_msg << "max_duration: #{max_duration};" if max_duration
- start_msg << "reload_page: #{reload_page}; sleep_interval: #{sleep_interval}; raise_on_failure: #{raise_on_failure}; retry_on_exception: #{retry_on_exception}"
+ start_msg.push(*[
+ "reload_page: #{reload_page};",
+ "sleep_interval: #{sleep_interval};",
+ "raise_on_failure: #{raise_on_failure};",
+ "retry_on_exception: #{retry_on_exception}"
+ ])
+
QA::Runtime::Logger.debug(start_msg.join(' '))
end
@@ -58,7 +72,7 @@ module QA
) do
result = yield
end
- QA::Runtime::Logger.debug("ended retry_until")
+ QA::Runtime::Logger.debug("ended retry_until") if log
result
end
diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb
index 2734a702536..bc905fdeadd 100644
--- a/qa/qa/tools/delete_subgroups.rb
+++ b/qa/qa/tools/delete_subgroups.rb
@@ -20,16 +20,10 @@ module QA
end
def run
- $stdout.puts 'Running...'
+ $stdout.puts 'Fetching subgroups for deletion...'
- # Fetch group's id
- group_id = fetch_group_id
-
- sub_groups_head_response = head Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", per_page: "100").url
- total_sub_group_pages = sub_groups_head_response.headers[:x_total_pages]
-
- sub_group_ids = fetch_subgroup_ids(group_id, total_sub_group_pages)
- $stdout.puts "Number of Sub Groups not already marked for deletion: #{sub_group_ids.length}"
+ sub_group_ids = fetch_subgroup_ids
+ $stdout.puts "\nNumber of Sub Groups not already marked for deletion: #{sub_group_ids.length}"
delete_subgroups(sub_group_ids) unless sub_group_ids.empty?
$stdout.puts "\nDone"
@@ -52,12 +46,20 @@ module QA
JSON.parse(group_search_response.body)["id"]
end
- def fetch_subgroup_ids(group_id, group_pages)
+ def fetch_subgroup_ids
+ group_id = fetch_group_id
sub_groups_ids = []
+ page_no = '1'
+
+ # When we reach the last page, the x-next-page header is a blank string
+ while page_no.present?
+ $stdout.print '.'
+
+ sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", page: page_no, per_page: '100').url
+ sub_groups_ids.concat(JSON.parse(sub_groups_response.body)
+ .reject { |subgroup| !subgroup["marked_for_deletion_on"].nil? }.map { |subgroup| subgroup['id'] })
- group_pages.to_i.times do |page_no|
- sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", page: (page_no + 1).to_s, per_page: "100").url
- sub_groups_ids.concat(JSON.parse(sub_groups_response.body).reject { |subgroup| !subgroup["marked_for_deletion_on"].nil? }.map { |subgroup| subgroup["id"] })
+ page_no = sub_groups_response.headers[:x_next_page].to_s
end
sub_groups_ids.uniq
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 4f0f93bf020..e25892a008f 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -17,6 +17,7 @@ QA::Runtime::AllureReport.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
Dir[::File.join(__dir__, "support/shared_examples/*.rb")].sort.each { |f| require f }
+Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].sort.each { |f| require f }
RSpec.configure do |config|
config.include QA::Support::Matchers::EventuallyMatcher
diff --git a/qa/spec/specs/allure_report_spec.rb b/qa/spec/specs/allure_report_spec.rb
index 34116ca6cbd..03bf77039cc 100644
--- a/qa/spec/specs/allure_report_spec.rb
+++ b/qa/spec/specs/allure_report_spec.rb
@@ -45,14 +45,18 @@ describe QA::Runtime::AllureReport do
let(:png_file) { 'png-file' }
let(:html_file) { 'html-file' }
let(:ci_job) { 'ee:relative 5' }
+ let(:versions) { { version: '14', revision: '6ced31db947' } }
before do
stub_env('CI', 'true')
stub_env('CI_JOB_NAME', ci_job)
+ stub_env('GITLAB_QA_ADMIN_ACCESS_TOKEN', 'token')
allow(Allure).to receive(:add_attachment)
allow(File).to receive(:open).with(png_path) { png_file }
allow(File).to receive(:open).with(html_path) { html_file }
+ allow(RestClient::Request).to receive(:execute) { double('response', code: 200, body: versions.to_json) }
+ allow(QA::Runtime::Scenario).to receive(:method_missing).with(:gitlab_address).and_return('gitlab.com')
described_class.configure!
end
@@ -61,7 +65,7 @@ describe QA::Runtime::AllureReport do
aggregate_failures do
expect(allure_config.results_directory).to eq('tmp/allure-results')
expect(allure_config.clean_results_directory).to eq(true)
- expect(allure_config.environment_properties).to be_a_kind_of(Hash)
+ expect(allure_config.environment_properties.call).to eq(versions)
expect(allure_config.environment).to eq('ee:relative')
end
end
diff --git a/qa/spec/support/formatters/test_stats_formatter_spec.rb b/qa/spec/support/formatters/test_stats_formatter_spec.rb
index fec7ec1c7c0..859d45a660b 100644
--- a/qa/spec/support/formatters/test_stats_formatter_spec.rb
+++ b/qa/spec/support/formatters/test_stats_formatter_spec.rb
@@ -18,12 +18,13 @@ describe QA::Support::Formatters::TestStatsFormatter do
let(:quarantined) { 'false' }
let(:influx_client) { instance_double('InfluxDB2::Client', create_write_api: influx_write_api) }
let(:influx_write_api) { instance_double('InfluxDB2::WriteApi', write: nil) }
+ let(:stage) { '1_manage' }
+ let(:file_path) { "./qa/specs/features/#{stage}/subfolder/some_spec.rb" }
let(:influx_client_args) do
{
bucket: 'e2e-test-stats',
org: 'gitlab-qa',
- use_ssl: false,
precision: InfluxDB2::WritePrecision::NANOSECOND
}
end
@@ -34,14 +35,15 @@ describe QA::Support::Formatters::TestStatsFormatter do
time: DateTime.strptime(ci_timestamp).to_time,
tags: {
name: 'stats export spec',
- file_path: './spec/support/formatters/test_stats_formatter_spec.rb',
+ file_path: file_path.gsub('./qa/specs/features', ''),
status: :passed,
reliable: reliable,
quarantined: quarantined,
retried: "false",
job_name: "test-job",
merge_request: "false",
- run_type: run_type
+ run_type: run_type,
+ stage: stage
},
fields: {
id: './spec/support/formatters/test_stats_formatter_spec.rb[1:1]',
@@ -57,7 +59,9 @@ describe QA::Support::Formatters::TestStatsFormatter do
def run_spec(&spec)
spec ||= -> { it('spec') {} }
- describe_successfully('stats export', &spec)
+ describe_successfully('stats export', &spec).tap do |example_group|
+ example_group.examples.each { |ex| ex.metadata[:file_path] = file_path }
+ end
send_stop_notification
end
diff --git a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb
new file mode 100644
index 00000000000..6e197015640
--- /dev/null
+++ b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.shared_context 'packages registry qa scenario' do
+ let(:personal_access_token) { Runtime::Env.personal_access_token }
+
+ let(:package_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = "#{package_type}_package_project"
+ project.initialize_with_readme = true
+ project.visibility = :private
+ end
+ end
+
+ let(:client_project) do
+ Resource::Project.fabricate_via_api! do |client_project|
+ client_project.name = "#{package_type}_client_project"
+ client_project.initialize_with_readme = true
+ client_project.group = package_project.group
+ end
+ end
+
+ let(:package) do
+ Resource::Package.init do |package|
+ package.name = package_name
+ package.project = package_project
+ end
+ end
+
+ let(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.name = "qa-runner-#{Time.now.to_i}"
+ runner.tags = ["runner-for-#{package_project.group.name}"]
+ runner.executor = :docker
+ runner.token = package_project.group.runners_token
+ end
+ end
+
+ let(:gitlab_address_with_port) do
+ uri = URI.parse(Runtime::Scenario.gitlab_address)
+ "#{uri.scheme}://#{uri.host}:#{uri.port}"
+ end
+
+ let(:project_deploy_token) do
+ Resource::DeployToken.fabricate_via_browser_ui! do |deploy_token|
+ deploy_token.name = 'helm-package-deploy-token'
+ deploy_token.project = package_project
+ end
+ end
+
+ before do
+ Flow::Login.sign_in_unless_signed_in
+ runner
+ end
+
+ after do
+ runner.remove_via_api!
+ package.remove_via_api!
+ package_project.remove_via_api!
+ client_project.remove_via_api!
+ end
+ end
+end