summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock14
-rw-r--r--qa/README.md2
-rw-r--r--qa/Rakefile6
-rw-r--r--qa/qa.rb7
-rw-r--r--qa/qa/fixtures/designs/banana_sample.gif (renamed from qa/spec/fixtures/banana_sample.gif)bin71759 -> 71759 bytes
-rw-r--r--qa/qa/fixtures/designs/tanuki.jpgbin0 -> 84616 bytes
-rw-r--r--qa/qa/fixtures/designs/update/tanuki.jpgbin0 -> 83907 bytes
-rw-r--r--qa/qa/fixtures/designs/values.pngbin0 -> 122101 bytes
-rw-r--r--qa/qa/flow/login.rb1
-rw-r--r--qa/qa/flow/merge_request.rb15
-rw-r--r--qa/qa/git/repository.rb138
-rw-r--r--qa/qa/page/component/design_management.rb43
-rw-r--r--qa/qa/page/component/issuable/sidebar.rb33
-rw-r--r--qa/qa/page/component/new_snippet.rb39
-rw-r--r--qa/qa/page/component/note.rb66
-rw-r--r--qa/qa/page/component/select2.rb2
-rw-r--r--qa/qa/page/component/snippet.rb38
-rw-r--r--qa/qa/page/dashboard/snippet/edit.rb9
-rw-r--r--qa/qa/page/dashboard/todos.rb33
-rw-r--r--qa/qa/page/main/sign_up.rb9
-rw-r--r--qa/qa/page/merge_request/show.rb80
-rw-r--r--qa/qa/page/profile/accounts/show.rb5
-rw-r--r--qa/qa/page/profile/ssh_keys.rb15
-rw-r--r--qa/qa/page/project/issue/show.rb12
-rw-r--r--qa/qa/page/project/job/show.rb6
-rw-r--r--qa/qa/page/project/new.rb6
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb4
-rw-r--r--qa/qa/page/project/packages/index.rb2
-rw-r--r--qa/qa/page/project/pipeline/index.rb14
-rw-r--r--qa/qa/page/project/pipeline/new.rb19
-rw-r--r--qa/qa/page/project/pipeline/show.rb19
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb4
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/page/project/snippet/index.rb29
-rw-r--r--qa/qa/page/project/web_ide/edit.rb14
-rw-r--r--qa/qa/page/project/wiki/show.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb35
-rw-r--r--qa/qa/resource/design.rb34
-rw-r--r--qa/qa/resource/events/base.rb7
-rw-r--r--qa/qa/resource/events/project.rb7
-rw-r--r--qa/qa/resource/file.rb13
-rw-r--r--qa/qa/resource/merge_request.rb46
-rw-r--r--qa/qa/resource/project.rb16
-rw-r--r--qa/qa/resource/project_snippet.rb7
-rw-r--r--qa/qa/resource/repository/push.rb5
-rw-r--r--qa/qa/resource/snippet.rb11
-rw-r--r--qa/qa/resource/ssh_key.rb9
-rw-r--r--qa/qa/resource/user.rb10
-rw-r--r--qa/qa/runtime/api/request.rb6
-rw-r--r--qa/qa/runtime/env.rb49
-rw-r--r--qa/qa/runtime/feature.rb169
-rw-r--r--qa/qa/service/praefect_manager.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb4
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb11
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb4
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb64
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb124
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb45
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb53
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb49
-rw-r--r--qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb4
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb65
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb80
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb40
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb50
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb31
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb65
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb109
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb4
-rw-r--r--qa/qa/specs/features/sanity/framework_spec.rb2
-rw-r--r--qa/qa/specs/helpers/quarantine.rb4
-rw-r--r--qa/qa/support/api.rb2
-rw-r--r--qa/qa/support/json_formatter.rb3
-rw-r--r--qa/qa/support/otp.rb5
-rw-r--r--qa/qa/support/retrier.rb17
-rw-r--r--qa/qa/support/run.rb43
-rw-r--r--qa/qa/support/ssh.rb62
-rw-r--r--qa/qa/support/wait_for_requests.rb12
-rw-r--r--qa/qa/tools/initialize_gitlab_auth.rb30
-rw-r--r--qa/spec/factory/resource/user_spec.rb2
-rw-r--r--qa/spec/git/location_spec.rb2
-rw-r--r--qa/spec/git/repository_spec.rb84
-rw-r--r--qa/spec/page/base_spec.rb2
-rw-r--r--qa/spec/page/element_spec.rb2
-rw-r--r--qa/spec/page/logging_spec.rb2
-rw-r--r--qa/spec/page/validator_spec.rb2
-rw-r--r--qa/spec/page/view_spec.rb2
-rw-r--r--qa/spec/resource/api_fabricator_spec.rb2
-rw-r--r--qa/spec/resource/base_spec.rb2
-rw-r--r--qa/spec/resource/events/base_spec.rb2
-rw-r--r--qa/spec/resource/events/project_spec.rb2
-rw-r--r--qa/spec/resource/repository/push_spec.rb2
-rw-r--r--qa/spec/resource/ssh_key_spec.rb2
-rw-r--r--qa/spec/resource/user_spec.rb2
-rw-r--r--qa/spec/runtime/api/client_spec.rb2
-rw-r--r--qa/spec/runtime/api/request_spec.rb2
-rw-r--r--qa/spec/runtime/application_settings_spec.rb2
-rw-r--r--qa/spec/runtime/env_spec.rb20
-rw-r--r--qa/spec/runtime/feature_spec.rb252
-rw-r--r--qa/spec/runtime/key/ecdsa_spec.rb2
-rw-r--r--qa/spec/runtime/key/ed25519_spec.rb2
-rw-r--r--qa/spec/runtime/key/rsa_spec.rb2
-rw-r--r--qa/spec/runtime/logger_spec.rb2
-rw-r--r--qa/spec/runtime/namespace_spec.rb2
-rw-r--r--qa/spec/runtime/release_spec.rb2
-rw-r--r--qa/spec/runtime/scenario_spec.rb2
-rw-r--r--qa/spec/scenario/actable_spec.rb2
-rw-r--r--qa/spec/scenario/bootable_spec.rb2
-rw-r--r--qa/spec/scenario/template_spec.rb2
-rw-r--r--qa/spec/scenario/test/instance/airgapped_spec.rb2
-rw-r--r--qa/spec/scenario/test/instance/all_spec.rb2
-rw-r--r--qa/spec/scenario/test/instance/smoke_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/github_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/instance_saml_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/kubernetes_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/ldap_spec.rb6
-rw-r--r--qa/spec/scenario/test/integration/mattermost_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/object_storage_spec.rb2
-rw-r--r--qa/spec/scenario/test/sanity/framework_spec.rb2
-rw-r--r--qa/spec/scenario/test/sanity/selectors_spec.rb2
-rw-r--r--qa/spec/specs/helpers/quarantine_spec.rb71
-rw-r--r--qa/spec/specs/parallel_runner_spec.rb2
-rw-r--r--qa/spec/specs/runner_spec.rb2
-rw-r--r--qa/spec/support/repeater_spec.rb2
-rw-r--r--qa/spec/support/retrier_spec.rb2
-rw-r--r--qa/spec/support/run_spec.rb27
-rw-r--r--qa/spec/support/ssh_spec.rb114
-rw-r--r--qa/spec/support/wait_for_requests_spec.rb3
-rw-r--r--qa/spec/support/waiter_spec.rb2
160 files changed, 2399 insertions, 537 deletions
diff --git a/qa/Gemfile b/qa/Gemfile
index d946b22a0e0..f00f26a5482 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa'
-gem 'activesupport', '~> 6.0.3.1' # This should stay in sync with the root's Gemfile
+gem 'activesupport', '~> 6.0.3.3' # This should stay in sync with the root's Gemfile
gem 'capybara', '~> 3.29.0'
gem 'capybara-screenshot', '~> 1.0.23'
gem 'rake', '~> 12.3.3'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 2356c90a0af..6cdedc3834d 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.0.3.1)
+ activesupport (6.0.3.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -38,7 +38,7 @@ GEM
concord (0.1.5)
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
- concurrent-ruby (1.1.6)
+ concurrent-ruby (1.1.7)
debase (0.2.4.1)
debase-ruby_core_source (>= 0.10.2)
debase-ruby_core_source (0.10.6)
@@ -52,7 +52,7 @@ GEM
http-accept (1.7.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
- i18n (1.8.2)
+ i18n (1.8.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
knapsack (1.17.1)
@@ -67,11 +67,11 @@ GEM
mime-types-data (3.2020.0425)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
- minitest (5.14.1)
+ minitest (5.14.2)
netrc (0.11.0)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
- parallel (1.17.0)
+ parallel (1.19.2)
parallel_tests (2.29.0)
parallel
parser (2.7.1.4)
@@ -145,13 +145,13 @@ GEM
procto (~> 0.0.2)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.3.0)
+ zeitwerk (2.4.0)
PLATFORMS
ruby
DEPENDENCIES
- activesupport (~> 6.0.3.1)
+ activesupport (~> 6.0.3.3)
airborne (~> 0.3.4)
capybara (~> 3.29.0)
capybara-screenshot (~> 1.0.23)
diff --git a/qa/README.md b/qa/README.md
index 7ed4d63a589..5070e1ee9bd 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -50,7 +50,7 @@ the browser to use. You will need to have Chrome (or Chromium) and
- [Best practices](../doc/development/testing_guide/best_practices.md)
- [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
- [Guidelines](../doc/development/testing_guide/index.md)
- - [Tests with special setup for local environemnts](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
+ - [Tests with special setup for local environments](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
### Run the end-to-end tests in a local development environment
diff --git a/qa/Rakefile b/qa/Rakefile
index 844d8ff898d..1ecce8fdce9 100644
--- a/qa/Rakefile
+++ b/qa/Rakefile
@@ -2,6 +2,7 @@ require_relative 'qa/tools/revoke_all_personal_access_tokens'
require_relative 'qa/tools/delete_subgroups'
require_relative 'qa/tools/generate_perf_testdata'
require_relative 'qa/tools/delete_test_ssh_keys'
+require_relative 'qa/tools/initialize_gitlab_auth'
desc "Revokes all personal access tokens"
task :revoke_personal_access_tokens do
@@ -13,6 +14,11 @@ task :delete_subgroups do
QA::Tools::DeleteSubgroups.new.run
end
+desc "Initialize GitLab with an access token"
+task :initialize_gitlab_auth, [:address] do |t, args|
+ QA::Tools::InitializeGitLabAuth.new(args).run
+end
+
desc "Generate Performance Testdata"
task :generate_perf_testdata, :type do |t, args|
args.with_defaults(type: :all)
diff --git a/qa/qa.rb b/qa/qa.rb
index f6e0ea5d615..f281a4b6ef4 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -18,6 +18,7 @@ module QA
autoload :Project, 'qa/flow/project'
autoload :Saml, 'qa/flow/saml'
autoload :User, 'qa/flow/user'
+ autoload :MergeRequest, 'qa/flow/merge_request'
end
##
@@ -91,6 +92,7 @@ module QA
autoload :UserGPG, 'qa/resource/user_gpg'
autoload :Visibility, 'qa/resource/visibility'
autoload :ProjectSnippet, 'qa/resource/project_snippet'
+ autoload :Design, 'qa/resource/design'
module KubernetesCluster
autoload :Base, 'qa/resource/kubernetes_cluster/base'
@@ -190,6 +192,7 @@ module QA
autoload :Projects, 'qa/page/dashboard/projects'
autoload :Groups, 'qa/page/dashboard/groups'
autoload :Welcome, 'qa/page/dashboard/welcome'
+ autoload :Todos, 'qa/page/dashboard/todos'
module Snippet
autoload :New, 'qa/page/dashboard/snippet/new'
@@ -260,6 +263,7 @@ module QA
module Pipeline
autoload :Index, 'qa/page/project/pipeline/index'
autoload :Show, 'qa/page/project/pipeline/show'
+ autoload :New, 'qa/page/project/pipeline/new'
end
module Tag
@@ -371,6 +375,7 @@ module QA
module Snippet
autoload :New, 'qa/page/project/snippet/new'
autoload :Show, 'qa/page/project/snippet/show'
+ autoload :Index, 'qa/page/project/snippet/index'
end
end
@@ -590,10 +595,12 @@ module QA
autoload :Api, 'qa/support/api'
autoload :Dates, 'qa/support/dates'
autoload :Repeater, 'qa/support/repeater'
+ autoload :Run, 'qa/support/run'
autoload :Retrier, 'qa/support/retrier'
autoload :Waiter, 'qa/support/waiter'
autoload :WaitForRequests, 'qa/support/wait_for_requests'
autoload :OTP, 'qa/support/otp'
+ autoload :SSH, 'qa/support/ssh'
end
end
diff --git a/qa/spec/fixtures/banana_sample.gif b/qa/qa/fixtures/designs/banana_sample.gif
index 1322ac92d14..1322ac92d14 100644
--- a/qa/spec/fixtures/banana_sample.gif
+++ b/qa/qa/fixtures/designs/banana_sample.gif
Binary files differ
diff --git a/qa/qa/fixtures/designs/tanuki.jpg b/qa/qa/fixtures/designs/tanuki.jpg
new file mode 100644
index 00000000000..f0df472663e
--- /dev/null
+++ b/qa/qa/fixtures/designs/tanuki.jpg
Binary files differ
diff --git a/qa/qa/fixtures/designs/update/tanuki.jpg b/qa/qa/fixtures/designs/update/tanuki.jpg
new file mode 100644
index 00000000000..162beda6c7b
--- /dev/null
+++ b/qa/qa/fixtures/designs/update/tanuki.jpg
Binary files differ
diff --git a/qa/qa/fixtures/designs/values.png b/qa/qa/fixtures/designs/values.png
new file mode 100644
index 00000000000..9ecb6e7b778
--- /dev/null
+++ b/qa/qa/fixtures/designs/values.png
Binary files differ
diff --git a/qa/qa/flow/login.rb b/qa/qa/flow/login.rb
index d4d5cc2dcfc..d23d8eaf097 100644
--- a/qa/qa/flow/login.rb
+++ b/qa/qa/flow/login.rb
@@ -23,6 +23,7 @@ module QA
end
def sign_in(as: nil, address: :gitlab, skip_page_validation: false)
+ Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform(&:signed_in?)
Runtime::Browser.visit(address, Page::Main::Login)
Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as, skip_page_validation: skip_page_validation) }
end
diff --git a/qa/qa/flow/merge_request.rb b/qa/qa/flow/merge_request.rb
new file mode 100644
index 00000000000..c26140000fe
--- /dev/null
+++ b/qa/qa/flow/merge_request.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module QA
+ module Flow
+ module MergeRequest
+ module_function
+
+ def enable_merge_trains
+ Page::Project::Menu.perform(&:go_to_general_settings)
+ Page::Project::Settings::Main.perform(&:expand_merge_requests_settings)
+ Page::Project::Settings::MergeRequest.perform(&:enable_merge_train)
+ end
+ end
+ end
+end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 035946b8471..0f7e4fbbc97 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -2,10 +2,8 @@
require 'cgi'
require 'uri'
-require 'open3'
require 'fileutils'
require 'tmpdir'
-require 'tempfile'
require 'securerandom'
module QA
@@ -13,8 +11,7 @@ module QA
class Repository
include Scenario::Actable
include Support::Repeater
-
- RepositoryCommandError = Class.new(StandardError)
+ include Support::Run
attr_writer :use_lfs, :gpg_key_id
attr_accessor :env_vars
@@ -59,8 +56,12 @@ module QA
self.username, self.password = default_credentials
end
+ def use_default_identity
+ configure_identity('GitLab QA', 'root@gitlab.com')
+ end
+
def clone(opts = '')
- clone_result = run("git clone #{opts} #{uri} ./", max_attempts: 3)
+ clone_result = run_git("git clone #{opts} #{uri} ./", max_attempts: 3)
return clone_result.response unless clone_result.success?
enable_lfs_result = enable_lfs if use_lfs?
@@ -70,7 +71,7 @@ module QA
def checkout(branch_name, new_branch: false)
opts = new_branch ? '-b' : ''
- run(%Q{git checkout #{opts} "#{branch_name}"}).to_s
+ run_git(%Q{git checkout #{opts} "#{branch_name}"}).to_s
end
def shallow_clone
@@ -78,8 +79,8 @@ module QA
end
def configure_identity(name, email)
- run(%Q{git config user.name "#{name}"})
- run(%Q{git config user.email #{email}})
+ run_git(%Q{git config user.name "#{name}"})
+ run_git(%Q{git config user.email #{email}})
end
def commit_file(name, contents, message)
@@ -93,83 +94,82 @@ module QA
::File.write(name, contents)
if use_lfs?
- git_lfs_track_result = run(%Q{git lfs track #{name} --lockable})
+ git_lfs_track_result = run_git(%Q{git lfs track #{name} --lockable})
return git_lfs_track_result.response unless git_lfs_track_result.success?
end
- git_add_result = run(%Q{git add #{name}})
+ git_add_result = run_git(%Q{git add #{name}})
git_lfs_track_result.to_s + git_add_result.to_s
end
def add_tag(tag_name)
- run("git tag #{tag_name}").to_s
+ run_git("git tag #{tag_name}").to_s
end
def delete_tag(tag_name)
- run(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
+ run_git(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
end
def commit(message)
- run(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
+ run_git(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
end
def commit_with_gpg(message)
- run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
+ run_git(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
end
def current_branch
- run("git rev-parse --abbrev-ref HEAD").to_s
+ run_git("git rev-parse --abbrev-ref HEAD").to_s
end
- def push_changes(branch = 'master')
- run("git push #{uri} #{branch}", max_attempts: 3).to_s
+ def push_changes(branch = 'master', push_options: nil)
+ cmd = ['git push']
+ cmd << push_options_hash_to_string(push_options)
+ cmd << uri
+ cmd << branch
+ run_git(cmd.compact.join(' '), max_attempts: 3).to_s
end
def push_all_branches
- run("git push --all").to_s
+ run_git("git push --all").to_s
end
def push_tags_and_branches(branches)
- run("git push --tags origin #{branches.join(' ')}").to_s
+ run_git("git push --tags origin #{branches.join(' ')}").to_s
end
def merge(branch)
- run("git merge #{branch}")
+ run_git("git merge #{branch}")
end
def init_repository
- run("git init")
+ run_git("git init")
end
def pull(repository = nil, branch = nil)
- run(['git', 'pull', repository, branch].compact.join(' '))
+ run_git(['git', 'pull', repository, branch].compact.join(' '))
end
def commits
- run('git log --oneline').to_s.split("\n")
+ run_git('git log --oneline').to_s.split("\n")
end
def use_ssh_key(key)
- @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
- File.binwrite(private_key_file, key.private_key)
- File.chmod(0700, private_key_file)
-
- @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
- keyscan_params = ['-H']
- keyscan_params << "-p #{uri.port}" if uri.port
- keyscan_params << uri.host
- res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
- return res.response unless res.success?
+ @ssh = Support::SSH.perform do |ssh|
+ ssh.key = key
+ ssh.uri = uri
+ ssh.setup(env: self.env_vars)
+ ssh
+ end
- self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
+ self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{ssh.private_key_file.path} -o UserKnownHostsFile=#{ssh.known_hosts_file.path}"}
end
def delete_ssh_key
return unless ssh_key_set?
- private_key_file.close(true)
- known_hosts_file.close(true)
+ ssh.delete
end
def push_with_git_protocol(version, file_name, file_content, commit_message = 'Initial commit')
@@ -184,13 +184,13 @@ module QA
def git_protocol=(value)
raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" unless %w[0 1 2].include?(value.to_s)
- run("git config protocol.version #{value}")
+ run_git("git config protocol.version #{value}")
end
def fetch_supported_git_protocol
# ls-remote is one command known to respond to Git protocol v2 so we use
# it to get output including the version reported via Git tracing
- result = run("git ls-remote #{uri}", env: "GIT_TRACE_PACKET=1", max_attempts: 3)
+ result = run_git("git ls-remote #{uri}", max_attempts: 3, env: [*self.env_vars, "GIT_TRACE_PACKET=1"])
result.response[/git< version (\d+)/, 1] || 'unknown'
end
@@ -205,21 +205,16 @@ module QA
run("cat #{file}").to_s
end
+ def delete_netrc
+ File.delete(netrc_file_path) if File.exist?(netrc_file_path)
+ end
+
private
- attr_reader :uri, :username, :password, :known_hosts_file,
- :private_key_file, :use_lfs
+ attr_reader :uri, :username, :password, :ssh, :use_lfs
alias_method :use_lfs?, :use_lfs
- Result = Struct.new(:command, :exitstatus, :response) do
- alias_method :to_s, :response
-
- def success?
- exitstatus == 0
- end
- end
-
def add_credentials?
return false if !username || !password
return true unless ssh_key_set?
@@ -228,7 +223,7 @@ module QA
end
def ssh_key_set?
- !private_key_file.nil?
+ ssh && !ssh.private_key_file.nil?
end
def enable_lfs
@@ -237,33 +232,11 @@ module QA
touch_gitconfig_result = run("touch #{tmp_home_dir}/.gitconfig")
return touch_gitconfig_result.response unless touch_gitconfig_result.success?
- git_lfs_install_result = run('git lfs install')
+ git_lfs_install_result = run_git('git lfs install')
touch_gitconfig_result.to_s + git_lfs_install_result.to_s
end
- def run(command_str, env: [], max_attempts: 1)
- command = [env_vars, *env, command_str, '2>&1'].compact.join(' ')
- result = nil
-
- repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
- Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
- output, status = Open3.capture2e(command)
- output.chomp!
- Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
-
- result = Result.new(command, status.exitstatus, output)
-
- result.success?
- end
-
- unless result.success?
- raise RepositoryCommandError, "The command #{result.command} failed (#{result.exitstatus}) with the following output:\n#{result.response}"
- end
-
- result
- end
-
def default_credentials
if ::QA::Runtime::User.ldap_user?
[Runtime::User.ldap_username, Runtime::User.ldap_password]
@@ -293,6 +266,23 @@ module QA
@tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
end
+ def push_options_hash_to_string(opts)
+ return if opts.nil?
+
+ prefix = "-o merge_request"
+ opts.each_with_object([]) do |(key, value), options|
+ if value.is_a?(Array)
+ value.each do |item|
+ options << "#{prefix}.#{key}=\"#{item}\""
+ end
+ elsif value == true
+ options << "#{prefix}.#{key}"
+ else
+ options << "#{prefix}.#{key}=\"#{value}\""
+ end
+ end.join(' ')
+ end
+
def netrc_file_path
@netrc_file_path ||= File.join(tmp_home_dir, '.netrc')
end
@@ -304,6 +294,10 @@ module QA
def netrc_already_contains_content?
read_netrc_content.grep(/^#{Regexp.escape(netrc_content)}$/).any?
end
+
+ def run_git(command_str, env: self.env_vars, max_attempts: 1)
+ run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ')
+ end
end
end
end
diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb
index a8a24bd3949..fafbda58b07 100644
--- a/qa/qa/page/component/design_management.rb
+++ b/qa/qa/page/component/design_management.rb
@@ -30,6 +30,17 @@ module QA
view 'app/assets/javascripts/design_management/components/list/item.vue' do
element :design_file_name
element :design_image
+ element :design_status_icon
+ end
+
+ view 'app/assets/javascripts/design_management/pages/index.vue' do
+ element :archive_button
+ element :design_checkbox
+ element :design_dropzone_content
+ end
+
+ view 'app/assets/javascripts/design_management/components/delete_button.vue' do
+ element :confirm_archiving_button
end
end
end
@@ -52,12 +63,14 @@ module QA
# It accepts a `class:` option, but that only works for class attributes
# It doesn't work as a CSS selector.
# So instead we use the name attribute as a locator
- page.attach_file("design_file", design_file_path, make_visible: { display: 'block' })
+ within_element(:design_dropzone_content) do
+ page.attach_file("design_file", design_file_path, make_visible: { display: 'block' })
+ end
filename = ::File.basename(design_file_path)
found = wait_until(reload: false, sleep_interval: 1) do
- image = find_element(:design_image)
+ image = find_element(:design_image, filename: filename)
has_element?(:design_file_name, text: filename) &&
image["complete"] &&
@@ -67,15 +80,41 @@ module QA
raise ElementNotFound, %Q(Attempted to attach design "#{filename}" but it did not appear) unless found
end
+ def update_design(filename)
+ filepath = ::File.join('qa', 'fixtures', 'designs', 'update', filename)
+ add_design(filepath)
+ end
+
def click_design(filename)
click_element(:design_file_name, text: filename)
end
+ def select_design(filename)
+ click_element(:design_checkbox, design: filename)
+ end
+
+ def archive_selected_designs
+ click_element(:archive_button)
+ click_element(:confirm_archiving_button)
+ end
+
def has_annotation?(note)
within_element_by_index(:design_discussion_content, 0) do
has_element?(:note_content, text: note)
end
end
+
+ def has_design?(filename)
+ has_element?(:design_file_name, text: filename)
+ end
+
+ def has_created_icon?
+ has_element?(:design_status_icon, status: 'file-addition-solid')
+ end
+
+ def has_modified_icon?
+ has_element?(:design_status_icon, status: 'file-modified-solid')
+ end
end
end
end
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
index 4e94049efe7..82347ee209a 100644
--- a/qa/qa/page/component/issuable/sidebar.rb
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -18,16 +18,29 @@ module QA
element :more_assignees_link
end
- base.view 'app/helpers/dropdowns_helper.rb' do
+ base.view 'app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue' do
+ element :labels_block
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_value.vue' do
+ element :selected_label_content
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents.vue' do
+ element :labels_dropdown_content
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do
+ element :labels_edit_button
+ end
+
+ base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue' do
element :dropdown_input_field
end
base.view 'app/views/shared/issuable/_sidebar.html.haml' do
element :assignee_block
- element :dropdown_menu_labels
- element :edit_labels_link
element :edit_milestone_link
- element :labels_block
element :milestone_block
element :milestone_link
end
@@ -64,7 +77,7 @@ module QA
def has_label?(label)
within_element(:labels_block) do
- !!has_element?(:label, label_name: label)
+ !!has_element?(:selected_label_content, label_name: label)
end
end
@@ -80,23 +93,25 @@ module QA
def select_labels_and_refresh(labels)
Support::Retrier.retry_until do
- click_element(:edit_labels_link)
- has_element?(:dropdown_menu_labels, text: labels.first)
+ click_element(:labels_edit_button)
+ has_element?(:labels_dropdown_content, text: labels.first)
end
labels.each do |label|
- within_element(:dropdown_menu_labels, text: label) do
+ within_element(:labels_dropdown_content) do
send_keys_to_element(:dropdown_input_field, [label, :enter])
end
end
- click_element(:edit_labels_link)
+ click_element(:labels_edit_button)
labels.each do |label|
has_element?(:labels_block, text: label, wait: 0)
end
refresh
+
+ wait_for_requests
end
def toggle_more_assignees_link
diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb
index 3e5ae29177a..741a3feb73b 100644
--- a/qa/qa/page/component/new_snippet.rb
+++ b/qa/qa/page/component/new_snippet.rb
@@ -21,18 +21,11 @@ module QA
base.view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do
element :file_name_field
+ element :file_holder_container
end
- base.view 'app/views/shared/form_elements/_description.html.haml' do
- element :issuable_form_description
- end
-
- base.view 'app/views/shared/snippets/_form.html.haml' do
- element :snippet_description_field
- element :description_placeholder
- element :snippet_title_field
- element :file_name_field
- element :submit_button
+ base.view 'app/assets/javascripts/snippets/components/snippet_blob_actions_edit.vue' do
+ element :add_file_button
end
base.view 'app/views/shared/_zen.html.haml' do
@@ -54,12 +47,28 @@ module QA
choose visibility
end
- def fill_file_name(name)
- fill_element :file_name_field, name
+ def fill_file_name(name, file_number = nil)
+ if file_number
+ within_element_by_index(:file_holder_container, file_number - 1) do
+ fill_element(:file_name_field, name)
+ end
+ else
+ fill_element(:file_name_field, name)
+ end
+ end
+
+ def fill_file_content(content, file_number = nil)
+ if file_number
+ within_element_by_index(:file_holder_container, file_number - 1) do
+ text_area.set(content)
+ end
+ else
+ text_area.set content
+ end
end
- def fill_file_content(content)
- text_area.set content
+ def click_add_file
+ click_element(:add_file_button)
end
def click_create_snippet_button
@@ -70,7 +79,7 @@ module QA
private
def text_area
- find('#editor textarea', visible: false)
+ find('.monaco-editor textarea', visible: false)
end
end
end
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
index 0e9cdd49519..e6defd2ec0c 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -9,9 +9,18 @@ module QA
def self.included(base)
super
+ base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
+ element :toggle_comments_button
+ end
+
+ base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do
+ element :discussion_reply_tab
+ element :resolve_discussion_button
+ end
+
base.view 'app/assets/javascripts/notes/components/comment_form.vue' do
element :note_dropdown
- element :discussion_option
+ element :discussion_menu_item
end
base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do
@@ -23,39 +32,32 @@ module QA
end
base.view 'app/assets/javascripts/notes/components/note_form.vue' do
- element :reply_input
+ element :reply_field
element :reply_comment_button
end
- base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do
- element :discussion_reply_tab
- element :resolve_discussion_button
- end
-
base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do
- element :expand_replies
- element :collapse_replies
+ element :expand_replies_button
+ element :collapse_replies_button
end
- base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
- element :toggle_comments_button
+ base.view 'app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue' do
+ element :skeleton_note_placeholder
end
end
- def start_discussion(text)
- fill_element :comment_input, text
- click_element :note_dropdown
- click_element :discussion_option
- click_element :comment_button
+ def collapse_replies
+ click_element :collapse_replies_button
end
- def toggle_comments(position)
- all_elements(:toggle_comments_button, minimum: position)[position - 1].click
+ def edit_comment(text)
+ click_element :note_edit_button
+ fill_element :reply_field, text
+ click_element :reply_comment_button
end
- def type_reply_to_discussion(position, reply_text)
- all_elements(:discussion_reply_tab, minimum: position)[position - 1].click
- fill_element :reply_input, reply_text
+ def expand_replies
+ click_element :expand_replies_button
end
def reply_to_discussion(position, reply_text)
@@ -69,18 +71,24 @@ module QA
end
end
- def collapse_replies
- click_element :collapse_replies
+ def start_discussion(text)
+ fill_element :comment_field, text
+ click_element :note_dropdown
+ click_element :discussion_menu_item
+ click_element :comment_button
end
- def expand_replies
- click_element :expand_replies
+ def toggle_comments(position)
+ all_elements(:toggle_comments_button, minimum: position)[position - 1].click
end
- def edit_comment(text)
- click_element :note_edit_button
- fill_element :reply_input, text
- click_element :reply_comment_button
+ def type_reply_to_discussion(position, reply_text)
+ all_elements(:discussion_reply_tab, minimum: position)[position - 1].click
+ fill_element :reply_field, reply_text
+ end
+
+ def wait_for_loading
+ has_no_element?(:skeleton_note_placeholer)
end
end
end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 761bbb17168..87aed0105aa 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -43,6 +43,8 @@ module QA
end
def wait_for_search_to_complete
+ Support::WaitForRequests.wait_for_requests
+
has_css?('.select2-active', wait: 1)
has_no_css?('.select2-active', wait: 30)
end
diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb
index 2776b6c078e..9a4b06d8ac7 100644
--- a/qa/qa/page/component/snippet.rb
+++ b/qa/qa/page/component/snippet.rb
@@ -98,15 +98,39 @@ module QA
end
end
- def has_file_name?(file_name)
- within_element(:file_title_content) do
- has_text?(file_name)
+ def has_file_name?(file_name, file_number = nil)
+ if file_number
+ within_element_by_index(:file_title_content, file_number - 1) do
+ has_text?(file_name)
+ end
+ else
+ within_element(:file_title_content) do
+ has_text?(file_name)
+ end
end
end
- def has_file_content?(file_content)
- within_element(:file_content) do
- has_text?(file_content)
+ def has_file_content?(file_content, file_number = nil)
+ if file_number
+ within_element_by_index(:file_content, file_number - 1) do
+ has_text?(file_content)
+ end
+ else
+ within_element(:file_content) do
+ has_text?(file_content)
+ end
+ end
+ end
+
+ def has_no_file_content?(file_content, file_number = nil)
+ if file_number
+ within_element_by_index(:file_content, file_number - 1) do
+ has_no_text?(file_content)
+ end
+ else
+ within_element(:file_content) do
+ has_no_text?(file_content)
+ end
end
end
@@ -115,7 +139,7 @@ module QA
end
def click_edit_button
- click_element(:snippet_action_button, action: 'Edit')
+ click_element(:snippet_action_button, Page::Dashboard::Snippet::Edit, action: 'Edit')
end
def click_delete_button
diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb
index 7802a680c25..37c0747aea4 100644
--- a/qa/qa/page/dashboard/snippet/edit.rb
+++ b/qa/qa/page/dashboard/snippet/edit.rb
@@ -5,16 +5,11 @@ module QA
module Dashboard
module Snippet
class Edit < Page::Base
- view 'app/views/shared/snippets/_form.html.haml' do
- element :submit_button
- end
-
view 'app/assets/javascripts/snippets/components/edit.vue' do
- element :submit_button
+ element :submit_button, required: true
end
def add_to_file_content(content)
- finished_loading?
text_area.set content
text_area.has_text?(content) # wait for changes to take effect
end
@@ -30,7 +25,7 @@ module QA
private
def text_area
- find('#editor textarea', visible: false)
+ find('.monaco-editor textarea', visible: false)
end
end
end
diff --git a/qa/qa/page/dashboard/todos.rb b/qa/qa/page/dashboard/todos.rb
new file mode 100644
index 00000000000..d8baadcf73d
--- /dev/null
+++ b/qa/qa/page/dashboard/todos.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ class Todos < Page::Base
+ include Page::Component::Snippet
+
+ view 'app/views/dashboard/todos/index.html.haml' do
+ element :todos_list_container, required: true
+ end
+
+ view 'app/views/dashboard/todos/_todo.html.haml' do
+ element :todo_item_container
+ element :todo_action_name_content
+ element :todo_target_title_content
+ end
+
+ def has_todo_list?
+ has_element? :todo_item_container
+ end
+
+ def has_latest_todo_item_with_content?(action, title)
+ within_element(:todos_list_container) do
+ within_element_by_index(:todo_item_container, 0) do
+ has_element?(:todo_action_name_content, text: action) && has_element?(:todo_target_title_content, text: title)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index b7808afb209..98bbbc53027 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -5,12 +5,12 @@ module QA
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
- element :new_user_name_field
+ element :new_user_first_name_field
+ element :new_user_last_name_field
element :new_user_username_field
element :new_user_email_field
element :new_user_password_field
element :new_user_register_button
- element :new_user_accept_terms_checkbox
end
view 'app/views/registrations/welcome.html.haml' do
@@ -18,13 +18,12 @@ module QA
end
def sign_up!(user)
- fill_element :new_user_name_field, user.name
+ fill_element :new_user_first_name_field, user.first_name
+ fill_element :new_user_last_name_field, user.last_name
fill_element :new_user_username_field, user.username
fill_element :new_user_email_field, user.email
fill_element :new_user_password_field, user.password
- check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox)
-
signed_in = retry_until do
click_element :new_user_register_button if has_element?(:new_user_register_button)
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 0b80ba84fa4..164f25389c0 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -44,15 +44,23 @@ module QA
element :squash_checkbox
end
- view 'app/assets/javascripts/vue_shared/components/notes/skeleton_note.vue' do
- element :skeleton_note
- end
-
view 'app/views/projects/merge_requests/show.html.haml' do
element :notes_tab
element :diffs_tab
end
+ view 'app/assets/javascripts/diffs/components/compare_dropdown_layout.vue' do
+ element :dropdown_content
+ end
+
+ view 'app/assets/javascripts/diffs/components/compare_versions.vue' do
+ element :target_version_dropdown
+ end
+
+ view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
+ element :file_name_content
+ end
+
view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do
element :new_diff_line
end
@@ -67,15 +75,13 @@ module QA
view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do
element :review_bar
- element :discard_review
- element :modal_delete_pending_comments
end
view 'app/assets/javascripts/notes/components/note_form.vue' do
element :unresolve_review_discussion_checkbox
element :resolve_review_discussion_checkbox
- element :start_review
- element :comment_now
+ element :start_review_button
+ element :comment_now_button
end
view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
@@ -83,46 +89,54 @@ module QA
end
def start_review
- click_element :start_review
+ click_element(:start_review_button)
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
- has_no_element? :start_review
+ has_no_element?(:start_review_button)
+ end
+
+ def click_target_version_dropdown
+ click_element(:target_version_dropdown)
end
def comment_now
- click_element :comment_now
+ click_element(:comment_now_button)
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
- has_no_element? :comment_now
+ has_no_element?(:comment_now_button)
+ end
+
+ def version_dropdown_content
+ find_element(:dropdown_content).text
end
def submit_pending_reviews
- within_element :review_bar do
- click_element :review_preview_toggle
- click_element :submit_review
+ within_element(:review_bar) do
+ click_element(:review_preview_toggle)
+ click_element(:submit_review)
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
- has_no_element? :submit_review
+ has_no_element?(:submit_review)
end
end
def discard_pending_reviews
- within_element :review_bar do
- click_element :discard_review
+ within_element(:review_bar) do
+ click_element(:discard_review)
end
- click_element :modal_delete_pending_comments
+ click_element(:modal_delete_pending_comments)
end
def resolve_review_discussion
- scroll_to_element :start_review
- check_element :resolve_review_discussion_checkbox
+ scroll_to_element(:start_review_button)
+ check_element(:resolve_review_discussion_checkbox)
end
def unresolve_review_discussion
- check_element :unresolve_review_discussion_checkbox
+ check_element(:unresolve_review_discussion_checkbox)
end
def add_comment_to_diff(text)
@@ -131,7 +145,7 @@ module QA
end
all_elements(:new_diff_line, minimum: 1).first.hover
click_element(:diff_comment)
- fill_element(:reply_input, text)
+ fill_element(:reply_field, text)
end
def click_discussions_tab
@@ -160,6 +174,10 @@ module QA
has_no_text?('Fast-forward merge is not possible')
end
+ def has_file?(file_name)
+ has_element?(:file_name_content, text: file_name)
+ end
+
def has_merge_button?
refresh
@@ -168,7 +186,7 @@ module QA
def has_pipeline_status?(text)
# Pipelines can be slow, so we wait a bit longer than the usual 10 seconds
- has_element?(:merge_request_pipeline_info_content, text: text, wait: 30)
+ has_element?(:merge_request_pipeline_info_content, text: text, wait: 60)
end
def has_title?(title)
@@ -190,7 +208,7 @@ module QA
!find_element(:squash_checkbox).disabled?
end
- click_element :squash_checkbox
+ click_element(:squash_checkbox)
end
def merge!
@@ -202,7 +220,7 @@ module QA
end
def merged?
- has_element?(:merged_status_content, text: 'The changes were merged into', wait: 30)
+ has_element?(:merged_status_content, text: 'The changes were merged into', wait: 60)
end
# Check if the MR is able to be merged
@@ -235,7 +253,7 @@ module QA
!find_element(:mr_rebase_button).disabled?
end
- click_element :mr_rebase_button
+ click_element(:mr_rebase_button)
success = wait_until do
has_text?('Fast-forward merge without a merge commit')
@@ -251,12 +269,12 @@ module QA
end
def view_email_patches
- click_element :download_dropdown
+ click_element(:download_dropdown)
visit_link_in_element(:download_email_patches)
end
def view_plain_diff
- click_element :download_dropdown
+ click_element(:download_dropdown)
visit_link_in_element(:download_plain_diff)
end
@@ -266,10 +284,6 @@ module QA
end
end
- def wait_for_loading
- has_no_element?(:skeleton_note)
- end
-
def click_open_in_web_ide
click_element(:open_in_web_ide_button)
wait_for_requests
diff --git a/qa/qa/page/profile/accounts/show.rb b/qa/qa/page/profile/accounts/show.rb
index cf7f7d80cfa..84a34d1da78 100644
--- a/qa/qa/page/profile/accounts/show.rb
+++ b/qa/qa/page/profile/accounts/show.rb
@@ -7,6 +7,7 @@ module QA
class Show < Page::Base
view 'app/views/profiles/accounts/show.html.haml' do
element :delete_account_button, required: true
+ element :enable_2fa_button
end
view 'app/assets/javascripts/profile/account/components/delete_account_modal.vue' do
@@ -14,6 +15,10 @@ module QA
element :confirm_delete_account_button
end
+ def click_enable_2fa_button
+ click_element(:enable_2fa_button)
+ end
+
def delete_account(password)
click_element(:delete_account_button)
diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb
index 810877e21ad..8da484003f4 100644
--- a/qa/qa/page/profile/ssh_keys.rb
+++ b/qa/qa/page/profile/ssh_keys.rb
@@ -11,8 +11,9 @@ module QA
element :add_key_button
end
- view 'app/views/profiles/keys/_key_details.html.haml' do
- element :delete_key_button
+ view 'app/helpers/ssh_keys_helper.rb' do
+ element :delete_ssh_key_button
+ element :ssh_key_delete_modal
end
view 'app/views/profiles/keys/_key_table.html.haml' do
@@ -38,8 +39,14 @@ module QA
def remove_key(title)
click_link(title)
- accept_alert do
- click_element(:delete_key_button)
+ click_element(:delete_ssh_key_button)
+
+ # Retrying due to https://gitlab.com/gitlab-org/gitlab/-/issues/255287
+ retry_on_exception do
+ wait_for_animated_element(:ssh_key_delete_modal)
+ within_element(:ssh_key_delete_modal) do
+ click_button('Delete')
+ end
end
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 826acaa2e0a..a02617def9e 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -12,7 +12,7 @@ module QA
view 'app/assets/javascripts/notes/components/comment_form.vue' do
element :comment_button
- element :comment_input
+ element :comment_field
end
view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
@@ -43,7 +43,7 @@ module QA
end
view 'app/assets/javascripts/related_issues/components/related_issuable_input.vue' do
- element :add_issue_input
+ element :add_issue_field
end
view 'app/assets/javascripts/related_issues/components/related_issues_block.vue' do
@@ -57,8 +57,8 @@ module QA
def relate_issue(issue)
click_element(:related_issues_plus_button)
- fill_element(:add_issue_input, issue.web_url)
- send_keys_to_element(:add_issue_input, :enter)
+ fill_element(:add_issue_field, issue.web_url)
+ send_keys_to_element(:add_issue_field, :enter)
end
def related_issuable_item
@@ -84,7 +84,7 @@ module QA
# attachment option should be an absolute path
def comment(text, attachment: nil, filter: :all_activities)
method("select_#{filter}_filter").call
- fill_element :comment_input, "#{text}\n"
+ fill_element :comment_field, "#{text}\n"
unless attachment.nil?
QA::Page::Component::Dropzone.new(self, '.new-note')
@@ -125,6 +125,8 @@ module QA
click_element(:title)
click_element :discussion_filter
find_element(:filter_options, text: text).click
+
+ wait_for_loading
end
end
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 6a657b4ab39..2ecb27e05b2 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -58,6 +58,10 @@ module QA
click_element :retry_button
end
+ def has_job_log?
+ has_element? :job_log_content
+ end
+
private
def loaded?(wait: 60)
@@ -70,3 +74,5 @@ module QA
end
end
end
+
+QA::Page::Project::Job::Show.prepend_if_ee('QA::EE::Page::Project::Job::Show')
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index f6c015f64ea..7e296528795 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -23,11 +23,7 @@ module QA
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/projects/_import_project_pane.html.haml' do
- element :import_github, "icon('github', text: 'GitHub')" # rubocop:disable QA/ElementWithPattern
- end
-
- view 'app/views/projects/project_templates/_built_in_templates.html.haml' do
+ view 'app/views/projects/project_templates/_template.html.haml' do
element :use_template_button
element :template_option_row
end
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 0c92f9a9f28..114e3ddd46a 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -7,11 +7,11 @@ module QA
module Kubernetes
class Index < Page::Base
view 'app/views/clusters/clusters/_empty_state.html.haml' do
- element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
+ element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Integrate with a cluster certificate')" # rubocop:disable QA/ElementWithPattern
end
def add_kubernetes_cluster
- click_on 'Add Kubernetes cluster'
+ click_on 'Connect cluster with certificate'
end
def has_cluster?(cluster)
diff --git a/qa/qa/page/project/packages/index.rb b/qa/qa/page/project/packages/index.rb
index 6d55d1d04b6..396d3373b8a 100644
--- a/qa/qa/page/project/packages/index.rb
+++ b/qa/qa/page/project/packages/index.rb
@@ -26,3 +26,5 @@ module QA
end
end
end
+
+QA::Page::Project::Packages::Index.prepend_if_ee('QA::EE::Page::Project::Packages::Index')
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index aa2ef2f058f..aff2378330a 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -14,6 +14,10 @@ module QA
element :pipeline_retry_button
end
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue' do
+ element :run_pipeline_button
+ end
+
def click_on_latest_pipeline
all_elements(:pipeline_url_link, minimum: 1, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).first.click
end
@@ -40,8 +44,18 @@ module QA
wait_for_latest_pipeline_success
end
end
+
+ def has_pipeline?
+ has_element? :pipeline_url_link
+ end
+
+ def click_run_pipeline_button
+ click_element :run_pipeline_button, Page::Project::Pipeline::New
+ end
end
end
end
end
end
+
+QA::Page::Project::Pipeline::Index.prepend_if_ee('QA::EE::Page::Project::Pipeline::Index')
diff --git a/qa/qa/page/project/pipeline/new.rb b/qa/qa/page/project/pipeline/new.rb
new file mode 100644
index 00000000000..644a21b46e9
--- /dev/null
+++ b/qa/qa/page/project/pipeline/new.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Pipeline
+ class New < QA::Page::Base
+ view 'app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue' do
+ element :run_pipeline_button, required: true
+ end
+
+ def click_run_pipeline_button
+ click_element :run_pipeline_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 57ab7fb4480..0fb5238a308 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -8,7 +8,7 @@ module QA
include Component::CiBadgeLink
view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
- element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern
+ element :pipeline_header
end
view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
@@ -16,8 +16,9 @@ module QA
end
view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
- element :job_component, /class.*ci-job-component.*/ # rubocop:disable QA/ElementWithPattern
+ element :job_item_container
element :job_link
+ element :action_button
end
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
@@ -34,16 +35,18 @@ module QA
end
def running?(wait: 0)
- within('.ci-header-container') do
+ within_element(:pipeline_header) do
page.has_content?('running', wait: wait)
end
end
def has_build?(name, status: :success, wait: nil)
- within('.pipeline-graph') do
- within('.ci-job-component', text: name) do
+ if status
+ within_element(:job_item_container, text: name) do
has_selector?(".ci-status-icon-#{status}", { wait: wait }.compact)
end
+ else
+ has_element?(:job_item_container, text: name)
end
end
@@ -78,6 +81,12 @@ module QA
def click_on_first_job
first('.js-pipeline-graph-job-link', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).click
end
+
+ def click_job_action(job_name)
+ within_element(:job_item_container, text: job_name) do
+ click_element(:action_button)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index aef9800e876..f2ced668a60 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -26,6 +26,10 @@ module QA
within_element(:ci_variable_key_field) { find('input').set key }
fill_element :ci_variable_value_field, value
click_ci_variable_save_button
+
+ wait_until(reload: false) do
+ within_element(:ci_variable_table_content) { has_element?(:edit_ci_variable_button) }
+ end
end
def click_add_variable
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index c607b35005e..d81be2803bd 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -103,6 +103,8 @@ module QA
end
def click_commit(commit_msg)
+ wait_for_requests
+
within_element(:file_tree_table) do
click_on commit_msg
end
diff --git a/qa/qa/page/project/snippet/index.rb b/qa/qa/page/project/snippet/index.rb
new file mode 100644
index 00000000000..a221abc4196
--- /dev/null
+++ b/qa/qa/page/project/snippet/index.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Snippet
+ class Index < Page::Base
+ include Page::Component::Snippet
+
+ view 'app/views/shared/snippets/_snippet.html.haml' do
+ element :snippet_link
+ end
+
+ def has_project_snippet?(title)
+ has_element?(:snippet_link, snippet_title: title)
+ end
+
+ def click_snippet_link(title)
+ within_element(:snippet_link, text: title) do
+ click_link(title)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Snippet::Index.prepend_if_ee('QA::EE::Page::Project::Snippet::Index')
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 56c8d343cf5..fc33c753230 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -73,6 +73,10 @@ module QA
element :project_path_content
end
+ view 'app/assets/javascripts/ide/components/commit_sidebar/message_field.vue' do
+ element :ide_commit_message_field
+ end
+
def has_file?(file_name)
within_element(:file_list) do
page.has_content? file_name
@@ -83,6 +87,10 @@ module QA
has_element?(:project_path_content, project_path: project_path)
end
+ def go_to_project
+ click_element(:project_path_content, Page::Project::Show)
+ end
+
def create_new_file_from_template(file_name, template)
click_element(:new_file, Page::Component::WebIDE::Modal::CreateNewFile)
@@ -115,7 +123,7 @@ module QA
find_element(:commit_sha_content).text
end
- def commit_changes(open_merge_request: false)
+ def commit_changes(commit_message = nil, open_merge_request: false)
# Clicking :begin_commit_button switches from the
# edit to the commit view
click_element(:begin_commit_button)
@@ -133,6 +141,10 @@ module QA
has_element?(:commit_button)
end
+ if commit_message
+ fill_element(:ide_commit_message_field, commit_message)
+ end
+
if open_merge_request
click_element(:commit_button, Page::MergeRequest::New)
else
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index cdd18e420d1..61b0d202a76 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -11,12 +11,12 @@ module QA
view 'app/views/shared/wikis/show.html.haml' do
element :wiki_page_title
element :wiki_page_content
+ element :edit_page_button
end
view 'app/views/shared/wikis/_main_links.html.haml' do
element :new_page_button
element :page_history_button
- element :edit_page_button
end
view 'app/views/shared/empty_states/_wikis.html.haml' do
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index d0cbab70983..034feb4e90f 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -96,15 +96,38 @@ module QA
end
def api_post
- response = post(
- Runtime::API::Request.new(api_client, api_post_path).url,
- api_post_body)
+ if api_post_path == "/graphql"
+ graphql_response = post(
+ Runtime::API::Request.new(api_client, api_post_path).url,
+ query: api_post_body)
- unless response.code == HTTP_STATUS_CREATED
- raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ flattened_response = 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}`."
+ end
+
+ flattened_response[:web_url] = flattened_response.delete(:webUrl)
+ flattened_response[:id] = flattened_response.fetch(:id).split('/')[-1]
+
+ process_api_response(flattened_response)
+ else
+ response = post(
+ Runtime::API::Request.new(api_client, api_post_path).url,
+ api_post_body)
+
+ unless response.code == HTTP_STATUS_CREATED
+ raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ end
+
+ process_api_response(parse_body(response))
end
+ end
- process_api_response(parse_body(response))
+ def flatten_hash(param)
+ param.each_pair.reduce({}) do |a, (k, v)|
+ v.is_a?(Hash) ? a.merge(flatten_hash(v)) : a.merge(k.to_sym => v)
+ end
end
def api_delete
diff --git a/qa/qa/resource/design.rb b/qa/qa/resource/design.rb
new file mode 100644
index 00000000000..182985f2d9f
--- /dev/null
+++ b/qa/qa/resource/design.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class Design < Base
+ attr_reader :id
+ attr_accessor :filename
+
+ attribute :issue do
+ Issue.fabricate_via_api!
+ end
+
+ def initialize
+ @update = false
+ @filename = 'banana_sample.gif'
+ end
+
+ # TODO This will be replaced as soon as file uploads over GraphQL are implemented
+ def fabricate!
+ issue.visit!
+
+ Page::Project::Issue::Show.perform do |issue|
+ issue.add_design(filepath)
+ end
+ end
+
+ private
+
+ def filepath
+ ::File.absolute_path(::File.join('qa', 'fixtures', 'designs', @filename))
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/events/base.rb b/qa/qa/resource/events/base.rb
index 91ec0e59e37..4c5f54825b3 100644
--- a/qa/qa/resource/events/base.rb
+++ b/qa/qa/resource/events/base.rb
@@ -9,9 +9,12 @@ module QA
EventNotFoundError = Class.new(RuntimeError)
module Base
- def events(action: nil)
+ def events(action: nil, target_type: nil)
+ query = []
+ query << "action=#{CGI.escape(action)}" if action
+ query << "target_type=#{CGI.escape(target_type)}" if target_type
path = [api_get_events]
- path << "?action=#{CGI.escape(action)}" if action
+ path << "?#{query.join("&")}" unless query.empty?
parse_body(api_get_from("#{path.join}"))
end
diff --git a/qa/qa/resource/events/project.rb b/qa/qa/resource/events/project.rb
index 99c78254f42..8c97f66c663 100644
--- a/qa/qa/resource/events/project.rb
+++ b/qa/qa/resource/events/project.rb
@@ -6,6 +6,13 @@ module QA
module Project
include Events::Base
+ def wait_for_merge(title)
+ QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_merge with title "#{title}"])
+ wait_for_event do
+ events(action: 'accepted', target_type: 'merge_request').any? { |event| event[:target_title] == title }
+ end
+ end
+
def wait_for_push(commit_message)
QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_push with commit message "#{commit_message}"])
wait_for_event do
diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb
index 76c4c71c48d..f573f3e89f0 100644
--- a/qa/qa/resource/file.rb
+++ b/qa/qa/resource/file.rb
@@ -27,11 +27,14 @@ module QA
Page::Project::Show.perform(&:create_first_new_file!)
- Page::File::Form.perform do |form|
- form.add_name(@name)
- form.add_content(@content)
- form.add_commit_message(@commit_message)
- form.commit_changes
+ Page::Project::WebIDE::Edit.perform do |ide|
+ ide.add_file(@name, @content)
+ ide.commit_changes(@commit_message)
+ ide.go_to_project
+ end
+
+ Page::Project::Show.perform do |project|
+ project.click_file(@name)
end
end
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 6c0f4621dd9..dca8fb6dc6b 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'securerandom'
+require 'active_support/core_ext/object/blank'
module QA
module Resource
@@ -17,7 +18,12 @@ module QA
:labels,
:file_name,
:file_content
- attr_writer :no_preparation
+ attr_writer :no_preparation,
+ :wait_for_merge
+
+ attribute :merge_when_pipeline_succeeds
+ attribute :merge_status
+ attribute :state
attribute :project do
Project.fabricate! do |resource|
@@ -58,6 +64,7 @@ module QA
@file_content = "File Added"
@target_new_branch = true
@no_preparation = false
+ @wait_for_merge = true
end
def fabricate!
@@ -80,10 +87,17 @@ module QA
end
def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
populate(:target, :source) unless @no_preparation
+
super
end
+ def api_merge_path
+ "/projects/#{project.id}/merge_requests/#{id}/merge"
+ end
+
def api_get_path
"/projects/#{project.id}/merge_requests/#{id}"
end
@@ -100,6 +114,36 @@ module QA
title: @title
}
end
+
+ def merge_via_api!
+ Support::Waiter.wait_until(sleep_interval: 1) do
+ QA::Runtime::Logger.debug("Waiting until merge request with id '#{id}' can be merged")
+
+ reload!.api_resource[:merge_status] == 'can_be_merged'
+ end
+
+ Support::Retrier.retry_on_exception do
+ response = put(Runtime::API::Request.new(api_client, api_merge_path).url)
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceUpdateFailedError, "Could not merge. Request returned (#{response.code}): `#{response}`."
+ end
+
+ result = parse_body(response)
+
+ project.wait_for_merge(result[:title]) if @wait_for_merge
+
+ result
+ end
+ end
+
+ private
+
+ def transform_api_resource(api_resource)
+ raise ResourceNotFoundError if api_resource.blank?
+
+ super(api_resource)
+ end
end
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 0025ebb2fd5..163c0b40bb5 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -110,6 +110,10 @@ module QA
response.any? { |file| file[:path] == file_path }
end
+ def has_branch?(branch)
+ has_branches?(Array(branch))
+ end
+
def has_branches?(branches)
branches.all? do |branch|
response = get(Runtime::API::Request.new(api_client, "#{api_repository_branches_path}/#{branch}").url)
@@ -140,6 +144,10 @@ module QA
"#{api_get_path}/members"
end
+ def api_merge_requests_path
+ "#{api_get_path}/merge_requests"
+ end
+
def api_runners_path
"#{api_get_path}/runners"
end
@@ -223,6 +231,14 @@ module QA
result[:import_status]
end
+ def merge_requests
+ parse_body(get(Runtime::API::Request.new(api_client, api_merge_requests_path).url))
+ end
+
+ def merge_request_with_title(title)
+ merge_requests.find { |mr| mr[:title] == title }
+ end
+
def runners(tag_list: nil)
response = if tag_list
get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url
diff --git a/qa/qa/resource/project_snippet.rb b/qa/qa/resource/project_snippet.rb
index ce4be6445f1..6fa38baaa91 100644
--- a/qa/qa/resource/project_snippet.rb
+++ b/qa/qa/resource/project_snippet.rb
@@ -21,6 +21,13 @@ module QA
new_snippet.set_visibility(@visibility)
new_snippet.fill_file_name(@file_name)
new_snippet.fill_file_content(@file_content)
+
+ @files.each.with_index(2) do |file, i|
+ new_snippet.click_add_file
+ new_snippet.fill_file_name(file[:name], i)
+ new_snippet.fill_file_content(file[:content], i)
+ end
+
new_snippet.click_create_snippet_button
end
end
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index 1e5399fcc59..5266f8b9bea 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -11,7 +11,7 @@ module QA
:branch_name, :new_branch, :output, :repository_http_uri,
:repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name
- attr_writer :remote_branch, :gpg_key_id
+ attr_writer :remote_branch, :gpg_key_id, :merge_request_push_options
def initialize
@file_name = "file-#{SecureRandom.hex(8)}.txt"
@@ -24,6 +24,7 @@ module QA
@use_lfs = false
@tag_name = nil
@gpg_key_id = nil
+ @merge_request_push_options = nil
end
def remote_branch
@@ -95,7 +96,7 @@ module QA
end
@output += commit_to repository
- @output += repository.push_changes("#{branch_name}:#{remote_branch}")
+ @output += repository.push_changes("#{branch_name}:#{remote_branch}", push_options: @merge_request_push_options)
end
repository.delete_ssh_key
diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb
index 39be5e5cb7d..c4ea6447209 100644
--- a/qa/qa/resource/snippet.rb
+++ b/qa/qa/resource/snippet.rb
@@ -11,6 +11,11 @@ module QA
@visibility = 'Public'
@file_content = 'The snippet content'
@file_name = 'New snippet file name'
+ @files = []
+ end
+
+ def add_files
+ yield @files
end
def fabricate!
@@ -22,6 +27,12 @@ module QA
new_page.set_visibility(@visibility)
new_page.fill_file_name(@file_name)
new_page.fill_file_content(@file_content)
+
+ @files.each.with_index(2) do |file, i|
+ new_page.click_add_file
+ new_page.fill_file_name(file[:name], i)
+ new_page.fill_file_content(file[:content], i)
+ end
new_page.click_create_snippet_button
end
end
diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb
index d4e394954ce..fcd0a479fec 100644
--- a/qa/qa/resource/ssh_key.rb
+++ b/qa/qa/resource/ssh_key.rb
@@ -76,6 +76,15 @@ module QA
parse_body(response)[:title].include?(title)
end
end
+
+ private
+
+ def api_get
+ with_paginated_response_body(Runtime::API::Request.new(api_client, '/user/keys', per_page: '100').url) do |page|
+ key = page.find { |key| key[:title] == title }
+ break process_api_response(key) if key
+ end
+ end
end
end
end
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index 9768fc154a7..5cd4147e154 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -11,6 +11,8 @@ module QA
attribute :id
attribute :name
+ attribute :first_name
+ attribute :last_name
attribute :email
def initialize
@@ -34,6 +36,14 @@ module QA
@name ||= api_resource&.dig(:name) || "QA User #{unique_id}"
end
+ def first_name
+ name.split(' ').first
+ end
+
+ def last_name
+ name.split(' ').drop(1).join(' ')
+ end
+
def email
@email ||= begin
api_email = api_resource&.dig(:email)
diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb
index 724b499d32f..b58be354103 100644
--- a/qa/qa/runtime/api/request.rb
+++ b/qa/qa/runtime/api/request.rb
@@ -34,7 +34,11 @@ module QA
#
# Returns the relative path to the requested API resource
def request_path(path, version: API_VERSION, **query_string)
- full_path = ::File.join('/api', version, path)
+ full_path = if path == '/graphql'
+ ::File.join('/api', path)
+ else
+ ::File.join('/api', version, path)
+ end
if query_string.any?
full_path << (path.include?('?') ? '&' : '?')
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index c254be4800b..ddaf35a2d65 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -24,7 +24,7 @@ module QA
SUPPORTED_FEATURES
end
- def address_matches?(*options)
+ def context_matches?(*options)
return false unless Runtime::Scenario.attributes[:gitlab_address]
opts = {}
@@ -33,29 +33,38 @@ module QA
uri = URI(Runtime::Scenario.gitlab_address)
- if options.any?
- options.each do |option|
- opts[:domain] = 'gitlab' if option == :production
-
- if option.is_a?(Hash) && !option[:subdomain].nil?
- opts.merge!(option)
-
- opts[:subdomain] = case option[:subdomain]
- when Array
- "(#{option[:subdomain].join("|")})."
- when Regexp
- option[:subdomain]
- else
- "(#{option[:subdomain]})."
- end
- end
+ options.each do |option|
+ opts[:domain] = 'gitlab' if option == :production
+
+ if option.is_a?(Hash) && !option[:pipeline].nil? && !ci_project_name.nil?
+ return pipeline_matches?(option[:pipeline])
+
+ elsif option.is_a?(Hash) && !option[:subdomain].nil?
+ opts.merge!(option)
+
+ opts[:subdomain] = case option[:subdomain]
+ when Array
+ "(#{option[:subdomain].join("|")})."
+ when Regexp
+ option[:subdomain]
+ else
+ "(#{option[:subdomain]})."
+ end
end
end
uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/)
end
- alias_method :dot_com?, :address_matches?
+ alias_method :dot_com?, :context_matches?
+
+ def pipeline_matches?(pipeline_to_run_in)
+ Array(pipeline_to_run_in).any? { |pipeline| pipeline.to_s.casecmp?(pipeline_from_project_name) }
+ end
+
+ def pipeline_from_project_name
+ ci_project_name.to_s.start_with?('gitlab-qa') ? 'master' : ci_project_name
+ end
def additional_repository_storage
ENV['QA_ADDITIONAL_REPOSITORY_STORAGE']
@@ -81,6 +90,10 @@ module QA
ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN']
end
+ def ci_job_url
+ ENV['CI_JOB_URL']
+ end
+
def ci_project_name
ENV['CI_PROJECT_NAME']
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index 579c2293c51..a48bc216ac2 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -1,98 +1,125 @@
# frozen_string_literal: true
+require 'active_support/core_ext/object/blank'
+
module QA
module Runtime
- module Feature
- extend self
- extend Support::Api
+ class Feature
+ class << self
+ # Documentation: https://docs.gitlab.com/ee/api/features.html
- SetFeatureError = Class.new(RuntimeError)
- AuthorizationError = Class.new(RuntimeError)
+ include Support::Api
- def enable(key)
- QA::Runtime::Logger.info("Enabling feature: #{key}")
- set_feature(key, true)
- end
+ SetFeatureError = Class.new(RuntimeError)
+ AuthorizationError = Class.new(RuntimeError)
+ UnknownScopeError = Class.new(RuntimeError)
- def disable(key)
- QA::Runtime::Logger.info("Disabling feature: #{key}")
- set_feature(key, false)
- end
-
- def remove(key)
- request = Runtime::API::Request.new(api_client, "/features/#{key}")
- response = delete(request.url)
- unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT
- raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`."
+ def remove(key)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = delete(request.url)
+ unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT
+ raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`."
+ end
end
- end
-
- def enable_and_verify(key)
- set_and_verify(key, enable: true)
- end
- def disable_and_verify(key)
- set_and_verify(key, enable: false)
- end
+ def enable(key, **scopes)
+ set_and_verify(key, enable: true, **scopes)
+ end
- def enabled?(key)
- feature = JSON.parse(get_features).find { |flag| flag["name"] == key }
- feature && feature["state"] == "on"
- end
+ def disable(key, **scopes)
+ set_and_verify(key, enable: false, **scopes)
+ end
- def get_features
- request = Runtime::API::Request.new(api_client, "/features")
- response = get(request.url)
- response.body
- end
+ def enabled?(key, **scopes)
+ feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
+ feature && feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], scopes)
+ end
- private
+ private
- def api_client
- @api_client ||= begin
- if Runtime::Env.admin_personal_access_token
- Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token)
- else
- user = Resource::User.fabricate_via_api! do |user|
- user.username = Runtime::User.admin_username
- user.password = Runtime::User.admin_password
- end
+ def api_client
+ @api_client ||= Runtime::API::Client.as_admin
+ rescue Runtime::API::Client::AuthorizationError => e
+ raise AuthorizationError, "Administrator access is required to enable/disable feature flags. #{e.message}"
+ end
- unless user.admin?
- raise AuthorizationError, "Administrator access is required to enable/disable feature flags. User '#{user.username}' is not an administrator."
+ def enabled_scope?(gates, scopes)
+ scopes.each do |key, value|
+ case key
+ when :project, :group, :user
+ actors = gates.filter { |i| i['key'] == 'actors' }.first['value']
+ break actors.include?("#{key.to_s.capitalize}:#{value.id}")
+ when :feature_group
+ groups = gates.filter { |i| i['key'] == 'groups' }.first['value']
+ break groups.include?(value)
+ else
+ raise UnknownScopeError, "Unknown scope: #{key}"
end
-
- Runtime::API::Client.new(:gitlab, user: user)
end
end
- end
- # Change a feature flag and verify that the change was successful
- # Arguments:
- # key: The feature flag to set (as a string)
- # enable: `true` to enable the flag, `false` to disable it
- def set_and_verify(key, enable:)
- Support::Retrier.retry_on_exception(sleep_interval: 2) do
- enable ? enable(key) : disable(key)
+ def get_features
+ request = Runtime::API::Request.new(api_client, '/features')
+ response = get(request.url)
+ response.body
+ end
- is_enabled = nil
+ # Change a feature flag and verify that the change was successful
+ # Arguments:
+ # key: The feature flag to set (as a string)
+ # enable: `true` to enable the flag, `false` to disable it
+ # scopes: Any scope (user, project, group) to restrict the change to
+ def set_and_verify(key, enable:, **scopes)
+ msg = "#{enable ? 'En' : 'Dis'}abling feature: #{key}"
+ msg += " for scope \"#{scopes_to_s(scopes)}\"" if scopes.present?
+ QA::Runtime::Logger.info(msg)
- QA::Support::Waiter.wait_until(sleep_interval: 1) do
- is_enabled = enabled?(key)
- is_enabled == enable
- end
+ Support::Retrier.retry_on_exception(sleep_interval: 2) do
+ set_feature(key, enable, scopes)
+
+ is_enabled = nil
+
+ QA::Support::Waiter.wait_until(sleep_interval: 1) do
+ is_enabled = enabled?(key, scopes)
+ is_enabled == enable || !enable && scopes.present?
+ end
+
+ if is_enabled == enable
+ QA::Runtime::Logger.info("Successfully #{enable ? 'en' : 'dis'}abled and verified feature flag: #{key}")
+ else
+ raise SetFeatureError, "#{key} was not #{enable ? 'en' : 'dis'}abled!" if enable
- raise SetFeatureError, "#{key} was not #{enable ? 'enabled' : 'disabled'}!" unless is_enabled == enable
+ QA::Runtime::Logger.warn("Feature flag scope was removed but the flag is still enabled globally.")
+ end
+ end
+ end
- QA::Runtime::Logger.info("Successfully #{enable ? 'enabled' : 'disabled'} and verified feature flag: #{key}")
+ def set_feature(key, value, **scopes)
+ scopes[:project] = scopes[:project].full_path if scopes.key?(:project)
+ scopes[:group] = scopes[:group].full_path if scopes.key?(:group)
+ scopes[:user] = scopes[:user].username if scopes.key?(:user)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = post(request.url, scopes.merge({ value: value }))
+ unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
+ raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ end
end
- end
- def set_feature(key, value)
- request = Runtime::API::Request.new(api_client, "/features/#{key}")
- response = post(request.url, { value: value })
- unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
- raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ def scopes_to_s(**scopes)
+ key = scopes.each_key.first
+ s = "#{key}: "
+ case key
+ when :project, :group
+ s += scopes[key].full_path
+ when :user
+ s += scopes[key].username
+ when :feature_group
+ s += scopes[key]
+ else
+ raise UnknownScopeError, "Unknown scope: #{key}"
+ end
+
+ s
end
end
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index fb2019394b4..3a17acbb317 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -387,7 +387,7 @@ module QA
end
def verify_storage_move_to_praefect(repo_path, virtual_storage)
- wait_until_shell_command("docker exec #{@gitlab} bash -c 'tail -n 50 /var/log/gitlab/praefect/current'") do |line|
+ wait_until_shell_command("docker exec #{@praefect} bash -c 'tail -n 50 /var/log/gitlab/praefect/current'") do |line|
log = JSON.parse(line)
log['grpc.method'] == 'ReplicateRepository' && log['virtual_storage'] == virtual_storage && log['relative_path'] == repo_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 aa06947e93b..90f58090ccd 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
@@ -22,7 +22,7 @@ module QA
end
end
- after(:context) do
+ 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
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 4515e76539b..6654a35915f 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
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
context 'Gitaly' do
- describe 'Backend node recovery', :orchestrated, :gitaly_cluster, :skip_live_env do
+ describe 'Backend node recovery', :orchestrated, :gitaly_cluster, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/238186', type: :investigating } do
let(:praefect_manager) { Service::PraefectManager.new }
let(:project) do
Resource::Project.fabricate! do |project|
@@ -22,7 +22,7 @@ module QA
praefect_manager.reset_primary_to_original
end
- it 'recovers from dataloss', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/238186', type: :investigating }, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/978' do
+ it 'recovers from dataloss', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/978' do
# Create a new project with a commit and wait for it to replicate
praefect_manager.wait_for_replication(project.id)
diff --git a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
index 758ba582929..e96b9ad9258 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
@@ -4,7 +4,6 @@ module QA
RSpec.describe 'Create' do
describe 'Changing Gitaly repository storage', :requires_admin do
praefect_manager = Service::PraefectManager.new
- praefect_manager.gitlab = 'gitlab'
shared_examples 'repository storage move' do
it 'confirms a `finished` status after moving project repository storage' do
@@ -28,7 +27,6 @@ module QA
context 'when moving from one Gitaly storage to another', :orchestrated, :repository_storage, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/973' do
let(:source_storage) { { type: :gitaly, name: 'default' } }
let(:destination_storage) { { type: :gitaly, name: QA::Runtime::Env.additional_repository_storage } }
-
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move-status'
@@ -37,6 +35,10 @@ module QA
end
end
+ before do
+ praefect_manager.gitlab = 'gitlab'
+ end
+
it_behaves_like 'repository storage move'
end
@@ -46,7 +48,6 @@ module QA
context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/974' do
let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } }
let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } }
-
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'repo-storage-move'
@@ -56,6 +57,10 @@ module QA
end
end
+ before do
+ praefect_manager.gitlab = 'gitlab-gitaly-cluster'
+ end
+
it_behaves_like 'repository storage move'
end
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
index 29f131ac322..c3cb503ed3f 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb
@@ -17,12 +17,12 @@ module QA
end
before do
- Runtime::Feature.enable_and_verify('gitaly_distributed_reads')
+ Runtime::Feature.enable(:gitaly_distributed_reads)
praefect_manager.wait_for_replication(project.id)
end
after do
- Runtime::Feature.disable_and_verify('gitaly_distributed_reads')
+ Runtime::Feature.disable(:gitaly_distributed_reads)
end
it 'reads from each node', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/979' do
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb
new file mode 100644
index 00000000000..82a06780830
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Merge request push options' do
+ # If run locally on GDK, push options need to be enabled on the host with the following command:
+ #
+ # git config --global receive.advertisepushoptions true
+
+ branch = "push-options-test-#{SecureRandom.hex(8)}"
+ title = "MR push options test #{SecureRandom.hex(8)}"
+ commit_message = 'Add README.md'
+
+ project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-push-options'
+ project.initialize_with_readme = true
+ end
+
+ it 'sets labels', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1032' do
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.commit_message = commit_message
+ push.branch_name = branch
+ push.merge_request_push_options = {
+ create: true,
+ title: title,
+ label: %w[one two three]
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+ expect(merge_request[:labels]).to include('one').and include('two').and include('three')
+ end
+
+ context 'when labels are set already' do
+ it 'removes them', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1033' do
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_content = "Unlabel test #{SecureRandom.hex(8)}"
+ push.commit_message = commit_message
+ push.branch_name = branch
+ push.new_branch = false
+ push.merge_request_push_options = {
+ title: title,
+ unlabel: %w[one three]
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+
+ aggregate_failures do
+ expect(merge_request[:labels]).to include('two')
+ expect(merge_request[:labels]).not_to include('one')
+ expect(merge_request[:labels]).not_to include('three')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb
new file mode 100644
index 00000000000..dde4708874d
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Merge request push options' do
+ # If run locally on GDK, push options need to be enabled on the host with the following command:
+ #
+ # git config --global receive.advertisepushoptions true
+
+ let(:branch) { "push-options-test-#{SecureRandom.hex(8)}" }
+ let(:title) { "MR push options test #{SecureRandom.hex(8)}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-push-options'
+ project.initialize_with_readme = true
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = "runner-for-#{project.name}"
+ runner.tags = ["runner-for-#{project.name}"]
+ end
+ end
+
+ after do
+ runner.remove_via_api!
+ end
+
+ it 'sets merge when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1037' do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ no-op:
+ tags:
+ - "runner-for-#{project.name}"
+ script: sleep 999 # Leave the pipeline pending
+ YAML
+ }
+ ]
+ )
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = branch
+ push.merge_request_push_options = {
+ create: true,
+ merge_when_pipeline_succeeds: true,
+ title: title
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+
+ merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = project
+ mr.id = merge_request[:iid]
+ end
+
+ expect(merge_request.state).to eq('opened')
+ expect(merge_request.merge_status).to eq('checking')
+ expect(merge_request.merge_when_pipeline_succeeds).to be true
+ end
+
+ it 'merges when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1036' do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ no-op:
+ tags:
+ - "runner-for-#{project.name}"
+ script: echo 'OK'
+ YAML
+ }
+ ]
+ )
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = branch
+ push.merge_request_push_options = {
+ create: true,
+ merge_when_pipeline_succeeds: true,
+ title: title
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+ expect(merge_request[:merge_when_pipeline_succeeds]).to be true
+
+ merge_request = Support::Waiter.wait_until(sleep_interval: 5) do
+ mr = Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = project
+ mr.id = merge_request[:iid]
+ end
+
+ next unless mr.state == 'merged'
+
+ mr
+ end
+
+ expect(merge_request.state).to eq('merged')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb
new file mode 100644
index 00000000000..d6bd668fa8a
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Merge request push options' do
+ # If run locally on GDK, push options need to be enabled on the host with the following command:
+ #
+ # git config --global receive.advertisepushoptions true
+
+ let(:branch) { "push-options-test-#{SecureRandom.hex(8)}" }
+ let(:title) { "MR push options test #{SecureRandom.hex(8)}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-push-options'
+ project.initialize_with_readme = true
+ end
+ end
+
+ it 'removes the source branch', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1035' do
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = branch
+ push.merge_request_push_options = {
+ create: true,
+ remove_source_branch: true,
+ title: title
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+
+ merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = project
+ mr.id = merge_request[:iid]
+ end.merge_via_api!
+
+ expect(merge_request[:state]).to eq('merged')
+ expect(project).not_to have_branch(branch)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb
new file mode 100644
index 00000000000..6072fd8c1a2
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Merge request push options' do
+ # If run locally on GDK, push options need to be enabled on the host with the following command:
+ #
+ # git config --global receive.advertisepushoptions true
+
+ let(:title) { "MR push options test #{SecureRandom.hex(8)}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-push-options'
+ project.initialize_with_readme = true
+ end
+ end
+
+ it 'sets a target branch', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1034' do
+ target_branch = "push-options-test-target-#{SecureRandom.hex(8)}"
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = target_branch
+ push.file_content = "Target branch test target branch #{SecureRandom.hex(8)}"
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = "push-options-test-#{SecureRandom.hex(8)}"
+ push.file_content = "Target branch test source branch #{SecureRandom.hex(8)}"
+ push.merge_request_push_options = {
+ create: true,
+ title: title,
+ target: target_branch
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+ expect(merge_request[:target_branch]).to eq(target_branch)
+
+ merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = project
+ mr.id = merge_request[:iid]
+ end.merge_via_api!
+
+ expect(merge_request[:state]).to eq('merged')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb
new file mode 100644
index 00000000000..f49a8a229dc
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Merge request push options' do
+ # If run locally on GDK, push options need to be enabled on the host with the following command:
+ #
+ # git config --global receive.advertisepushoptions true
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'merge-request-push-options'
+ project.initialize_with_readme = true
+ end
+ end
+
+ it 'sets title and description', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1038' do
+ description = "This is a test of MR push options"
+ title = "MR push options test #{SecureRandom.hex(8)}"
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.branch_name = "push-options-test-#{SecureRandom.hex(8)}"
+ push.merge_request_push_options = {
+ create: true,
+ title: title,
+ description: description
+ }
+ end
+
+ merge_request = project.merge_request_with_title(title)
+
+ expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
+
+ aggregate_failures do
+ expect(merge_request[:title]).to eq(title)
+ expect(merge_request[:description]).to eq(description)
+ end
+
+ merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = project
+ mr.id = merge_request[:iid]
+ end.merge_via_api!
+
+ expect(merge_request[:state]).to eq('merged')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
index af155b22618..f86bbee05c2 100644
--- a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
@@ -13,7 +13,7 @@ module QA
Runtime::ApplicationSettings.restore_application_settings(:default_branch_name)
end
- it 'sets the default branch name for a new project' do
+ it 'sets the default branch name for a new project', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1018' do
project = Resource::Project.fabricate_via_api! do |project|
project.name = "default-branch-name"
project.initialize_with_readme = true
@@ -32,7 +32,7 @@ module QA
end
end
- it 'allows a project to be created via the CLI with a different default branch name' do
+ it 'allows a project to be created via the CLI with a different default branch name', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1019' do
project_name = "default-branch-name-via-cli-#{SecureRandom.hex(8)}"
group = Resource::Group.fabricate_via_api!
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index 4bd99b4820e..548933d2cde 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
@@ -28,6 +28,13 @@ module QA
end
end
+ after do
+ # Delete the .netrc file created during this test so that subsequent tests don't try to use the logins
+ Git::Repository.perform do |repository|
+ repository.delete_netrc
+ end
+ end
+
it 'download archives of each user project then check they are different', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/427' do
archive_checksums = {}
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
new file mode 100644
index 00000000000..e81ebd5fa9d
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/2fa_ssh_recovery_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage', :requires_admin, :skip_live_env do
+ describe '2FA' do
+ let!(:user) { Resource::User.fabricate_via_api! }
+ let!(:user_api_client) { Runtime::API::Client.new(:gitlab, user: user) }
+ let(:address) { QA::Runtime::Scenario.gitlab_address }
+ let(:uri) { URI.parse(address) }
+ let(:ssh_port) { uri.port == 80 ? '' : '2222' }
+ let!(:ssh_key) do
+ Resource::SSHKey.fabricate_via_api! do |resource|
+ resource.title = "key for ssh tests #{Time.now.to_f}"
+ resource.api_client = user_api_client
+ end
+ end
+
+ before do
+ enable_2fa_for_user(user)
+ end
+
+ it 'allows 2FA code recovery via ssh' do
+ recovery_code = Support::SSH.perform do |ssh|
+ ssh.key = ssh_key
+ ssh.uri = address.gsub(uri.port.to_s, ssh_port)
+ ssh.setup
+ output = ssh.reset_2fa_codes
+ output.scan(/([A-Za-z0-9]{16})\n/).flatten.first
+ end
+
+ Flow::Login.sign_in(as: user, skip_page_validation: true)
+ Page::Main::TwoFactorAuth.perform do |two_fa_auth|
+ two_fa_auth.set_2fa_code(recovery_code)
+ two_fa_auth.click_verify_code_button
+ end
+
+ expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy
+
+ Page::Main::Menu.perform(&:sign_out)
+ Flow::Login.sign_in(as: user, skip_page_validation: true)
+ Page::Main::TwoFactorAuth.perform do |two_fa_auth|
+ two_fa_auth.set_2fa_code(recovery_code)
+ two_fa_auth.click_verify_code_button
+ end
+
+ expect(page).to have_text('Invalid two-factor code')
+ end
+
+ def enable_2fa_for_user(user)
+ Flow::Login.while_signed_in(as: user) do
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform(&:click_account)
+ Page::Profile::Accounts::Show.perform(&:click_enable_2fa_button)
+
+ 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.click_register_2fa_app_button
+ two_fa_auth.click_proceed_button
+ end
+ end
+ end
+ end
+ end
+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 bd0d28c86be..e514507fcb6 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
@@ -18,7 +18,7 @@ module QA
QA::Resource::Group.fabricate_via_api! do |group|
group.sandbox = sandbox_group
group.api_client = owner_api_client
- group.name = 'group-with-2fa'
+ group.path = "group-with-2fa-#{SecureRandom.hex(8)}"
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index a10329d5936..863c394a9f9 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -41,7 +41,7 @@ module QA
context 'when using attachments in comments', :object_storage do
let(:gif_file_name) { 'banana_sample.gif' }
let(:file_to_attach) do
- File.absolute_path(File.join('spec', 'fixtures', gif_file_name))
+ File.absolute_path(File.join('qa', 'fixtures', 'designs', gif_file_name))
end
before do
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
index b011978dce6..c908b1c46a1 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Milestones' do
include Support::Dates
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb
index 5f7e28190b2..564cfbb8399 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Group milestone' do
include Support::Dates
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
index 489691b4d0c..99d547acb26 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Project milestone' do
include Support::Dates
diff --git a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb
index a2190a8cf41..13761244300 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Related issues' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
index ff2b4fa5364..051e8fcecbe 100644
--- a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
@@ -5,7 +5,7 @@ module QA
context 'Design Management' do
let(:issue) { Resource::Issue.fabricate_via_api! }
let(:design_filename) { 'banana_sample.gif' }
- let(:design) { File.absolute_path(File.join('spec', 'fixtures', design_filename)) }
+ let(:design) { File.absolute_path(File.join('qa', 'fixtures', 'designs', design_filename)) }
let(:annotation) { "This design is great!" }
before do
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb
new file mode 100644
index 00000000000..7090427e5a4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Design Management' do
+ let(:first_design) { Resource::Design.fabricate! }
+
+ let(:second_design) do
+ Resource::Design.fabricate! do |design|
+ design.issue = first_design.issue
+ design.filename = 'values.png'
+ end
+ end
+
+ let(:third_design) do
+ Resource::Design.fabricate! do |design|
+ design.issue = second_design.issue
+ design.filename = 'tanuki.jpg'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'user archives a design', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/274' do
+ third_design.issue.visit!
+
+ Page::Project::Issue::Show.perform do |issue|
+ issue.select_design(third_design.filename)
+
+ issue.archive_selected_designs
+
+ expect(issue).not_to have_design(third_design.filename)
+ expect(issue).to have_design(first_design.filename)
+ expect(issue).to have_design(second_design.filename)
+ end
+
+ Page::Project::Issue::Show.perform do |issue|
+ issue.select_design(second_design.filename)
+ issue.select_design(first_design.filename)
+
+ issue.archive_selected_designs
+
+ expect(issue).not_to have_design(first_design.filename)
+ expect(issue).not_to have_design(second_design.filename)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb
new file mode 100644
index 00000000000..135063b6644
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Design Management' do
+ let(:design) do
+ Resource::Design.fabricate! do |design|
+ design.filename = 'tanuki.jpg'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'user adds a design and modifies it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/273' do
+ design.issue.visit!
+
+ Page::Project::Issue::Show.perform do |issue|
+ expect(issue).to have_created_icon
+ end
+
+ Page::Project::Issue::Show.perform do |issue|
+ issue.update_design(design.filename)
+ expect(issue).to have_modified_icon
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb
new file mode 100644
index 00000000000..7844d0d7ccb
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_merge_ref_diff_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', :requires_admin do
+ describe 'View merge request merge-ref diff' do
+ let(:merge_request) do
+ Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.title = 'This is a merge request'
+ merge_request.description = '... for viewing merge-ref and merge-base diffs'
+ merge_request.file_content = 'This exists on the source branch only'
+ end
+ end
+
+ let(:new_file_name) { "added_file-#{SecureRandom.hex(8)}.txt" }
+
+ before do
+ commit_to_branch(merge_request.target_branch, new_file_name)
+ commit_to_branch(merge_request.source_branch, new_file_name)
+
+ Flow::Login.sign_in
+ end
+
+ context 'when the feature flag default_merge_ref_for_diffs is enabled' do
+ before do
+ Runtime::Feature.enable('default_merge_ref_for_diffs', project: merge_request.project)
+
+ merge_request.visit!
+ end
+
+ it 'views the merge-ref diff by default' do
+ Page::MergeRequest::Show.perform do |mr_page|
+ mr_page.click_diffs_tab
+ mr_page.click_target_version_dropdown
+
+ expect(mr_page.version_dropdown_content).to include('master (HEAD)')
+ expect(mr_page.version_dropdown_content).not_to include('master (base)')
+ expect(mr_page).to have_file(merge_request.file_name)
+ expect(mr_page).not_to have_file(new_file_name)
+ end
+ end
+ end
+
+ context 'when the feature flag default_merge_ref_for_diffs is disabled' do
+ before do
+ Runtime::Feature.disable('default_merge_ref_for_diffs', project: merge_request.project)
+
+ merge_request.visit!
+ end
+
+ it 'views the merge-base diff by default' do
+ Page::MergeRequest::Show.perform do |mr_page|
+ mr_page.click_diffs_tab
+ mr_page.click_target_version_dropdown
+
+ expect(mr_page.version_dropdown_content).to include('master (HEAD)')
+ expect(mr_page.version_dropdown_content).to include('master (base)')
+ expect(mr_page).to have_file(merge_request.file_name)
+ expect(mr_page).to have_file(new_file_name)
+ end
+ end
+ end
+
+ def commit_to_branch(branch, file)
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = merge_request.project
+ commit.branch = branch
+ commit.commit_message = "Add new file on #{branch}"
+ commit.add_files(
+ [
+ {
+ file_path: file,
+ content: "This exists on source and target branches"
+ }
+ ]
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index 43f4b080c73..5aa5f0fc0a3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -18,7 +18,6 @@ module QA
file.commit_message = commit_message_for_create
end
- expect(page).to have_content('The file has been successfully created.')
expect(page).to have_content(file_name)
expect(page).to have_content(file_content)
expect(page).to have_content(commit_message_for_create)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
index 8ac7285d70c..5781bf8a7f0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb
@@ -37,8 +37,10 @@ module QA
project.wait_for_push_new_branch
# Check that the push worked
- expect(page).to have_content(file_name)
- expect(page).to have_content(file_content)
+ Page::Project::Show.perform do |project_page|
+ expect(project_page).to have_file(file_name)
+ expect(project_page).to have_readme_content(file_content)
+ end
# And check that the correct Git protocol was used
expect(git_protocol_reported).to eq(git_protocol)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
index f96b424d233..45afa252305 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb
@@ -4,7 +4,6 @@ module QA
RSpec.describe 'Create' do
describe 'Push mirror a repository over HTTP' do
it 'configures and syncs LFS objects for a (push) mirrored repository', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/414' do
- Runtime::Feature.enable_and_verify('push_mirror_syncs_lfs')
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
@@ -37,8 +36,10 @@ module QA
# Check that the target project has the commit from the source
target_project.visit!
- expect(page).to have_content('README.md')
- expect(page).to have_content('The rendered file could not be displayed because it is stored in LFS')
+ Page::Project::Show.perform do |project_page|
+ expect(project_page).to have_file('README.md')
+ expect(project_page).to have_readme_content('The rendered file could not be displayed because it is stored in LFS')
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 2ebab7d2a30..222eb3771ad 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -41,7 +41,7 @@ module QA
retry_on_fail do
expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
- .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
+ .to raise_error(QA::Support::Run::CommandError, /remote: fatal: pack exceeds maximum allowed size/)
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index 8b6973e6cea..cf14017b7f1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -36,8 +36,10 @@ module QA
project.visit!
- expect(page).to have_content('README.md')
- expect(page).to have_content("This is a test project named #{project.name}")
+ Page::Project::Show.perform do |project_page|
+ expect(project_page).to have_file('README.md')
+ expect(project_page).to have_readme_content("This is a test project named #{project.name}")
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index d20abd658c6..54d00209cc7 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -35,7 +35,7 @@ module QA
roles: Resource::ProtectedBranch::Roles::NO_ONE
})
- expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
+ expect { push_new_file(branch_name) }.to raise_error(QA::Support::Run::CommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
index ef3b315506f..a3f6d521766 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
@@ -36,6 +36,10 @@ module QA
Flow::Login.sign_in
end
+ after do
+ ssh_key.remove_via_api!
+ end
+
it 'clones, pushes, and pulls a snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/826' do
Resource::Repository::Push.fabricate! do |push|
push.repository_http_uri = repository_uri_http
@@ -87,7 +91,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
- .to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
+ .to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository\./)
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
index 34f6b464f29..be56b870490 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
@@ -36,6 +36,10 @@ module QA
Flow::Login.sign_in
end
+ after do
+ ssh_key.remove_via_api!
+ end
+
it 'clones, pushes, and pulls a project snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/833' do
Resource::Repository::Push.fabricate! do |push|
push.repository_http_uri = repository_uri_http
@@ -86,7 +90,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
- .to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
+ .to raise_error(QA::Support::Run::CommandError, /fatal: Could not read from remote repository\./)
end
end
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
new file mode 100644
index 00000000000..50f2f4789fa
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+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/-/issues/842' do
+ Flow::Login.sign_in
+
+ Page::Main::Menu.perform do |menu|
+ menu.go_to_more_dropdown_option(:snippets_link)
+ end
+
+ Resource::Snippet.fabricate_via_browser_ui! do |snippet|
+ snippet.title = 'Personal snippet with multiple files'
+ snippet.description = 'Snippet description'
+ snippet.visibility = 'Public'
+ snippet.file_name = 'First file name'
+ snippet.file_content = 'First file content'
+
+ snippet.add_files do |files|
+ files.append(name: 'Second file name', content: 'Second file content')
+ files.append(name: 'Third file name', content: 'Third file content')
+ end
+ end
+
+ 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_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)
+ expect(snippet).to have_file_content('Second file content', 2)
+ expect(snippet).to have_file_name('Third file name', 3)
+ expect(snippet).to have_file_content('Third file content', 3)
+ end
+ end
+ end
+ end
+end
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 0a8f6e13b2e..d80fc4c5b95 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
@@ -21,8 +21,8 @@ module QA
expect(snippet).to have_file_name('markdown_file.md')
expect(snippet).to have_file_content('Snippet heading')
expect(snippet).to have_file_content('Gitlab link')
- expect(snippet).not_to have_file_content('###')
- expect(snippet).not_to have_file_content('https://gitlab.com/')
+ expect(snippet).to have_no_file_content('###')
+ expect(snippet).to have_no_file_content('https://gitlab.com/')
end
end
end
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
new file mode 100644
index 00000000000..7b4ec573f53
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+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/-/issues/1024' do
+ Flow::Login.sign_in
+
+ Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
+ snippet.title = 'Project snippet with multiple files'
+ snippet.description = 'Snippet description'
+ snippet.visibility = 'Private'
+ snippet.file_name = '01 file name'
+ snippet.file_content = '1 file content'
+
+ # Ten is the limit of files you can have under one snippet at the moment
+ snippet.add_files do |files|
+ (2..10).each do |i|
+ files.append(name: file_name(i), content: file_content(i))
+ end
+ end
+ end
+
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ aggregate_failures 'file content verification' do
+ expect(snippet).to have_snippet_title('Project snippet with multiple files')
+ expect(snippet).to have_snippet_description('Snippet description')
+ expect(snippet).to have_visibility_type(/private/i)
+
+ (1..10).each do |i|
+ expect(snippet).to have_file_name(file_name(i), i)
+ expect(snippet).to have_file_content(file_content(i), i)
+ end
+ end
+ end
+ end
+
+ # Currently the files are returned in alphabetical order and not in the order they are created.
+ # However, it might soon change - see https://gitlab.com/gitlab-org/gitlab/-/issues/250836.
+ # By using a leading "0" we make sure the test works with either implementation.
+ def file_name(index)
+ "#{index.to_s.rjust(2, '0')} file name"
+ end
+
+ def file_content(index)
+ "#{index} file content"
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
index ea821f8b3e6..f7a2e3081fb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
@@ -10,7 +10,6 @@ module QA
end
end
- let(:web_ide_url) { current_url + '-/ide/project/' + project.path_with_namespace }
let(:file_name) { 'the very first file.txt' }
before do
@@ -18,10 +17,8 @@ module QA
end
it "creates the first file in an empty project via Web IDE", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/847' do
- # In the first iteration, the test opens Web IDE by modifying the URL to address past regressions.
- # Once the Web IDE button is introduced for empty projects, the test will be modified to go through UI.
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/27915 and https://gitlab.com/gitlab-org/gitlab/-/issues/27535.
- page.visit(web_ide_url)
+ project.visit!
+ Page::Project::Show.perform(&:create_first_new_file!)
Page::Project::WebIDE::Edit.perform do |ide|
ide.create_first_file(file_name)
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb
index e8053600930..4f1d9ac1696 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_directory_management_spec.rb
@@ -4,7 +4,7 @@ module QA
RSpec.describe 'Create' do
context 'Wiki' do
let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
- let(:new_path) { "a/new/path" }
+ let(:new_path) { "a/new/path-with-spaces" }
before do
Flow::Login.sign_in
@@ -23,7 +23,9 @@ module QA
Page::Project::Wiki::Edit.perform(&:click_save_changes)
Page::Project::Wiki::Show.perform do |wiki|
- expect(wiki).to have_directory(new_path)
+ expect(wiki).to have_directory('a')
+ expect(wiki).to have_directory('new')
+ expect(wiki).to have_directory('path with spaces')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
index cc4d0e1f6b5..ccd4d34a916 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Verify' do
describe 'Add or Remove CI variable via UI', :smoke do
- let!(:project) do
+ let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-ci-variables'
project.description = 'project with CI variables'
@@ -12,31 +12,27 @@ module QA
before do
Flow::Login.sign_in
+ project.visit!
add_ci_variable
- open_ci_cd_settings
end
it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/395' do
- Page::Project::Settings::CICD.perform do |settings|
- settings.expand_ci_variables do |page|
- expect(page).to have_text('VARIABLE_KEY')
- expect(page).not_to have_text('some_CI_variable')
+ Page::Project::Settings::CiVariables.perform do |ci_variable|
+ expect(ci_variable).to have_text('VARIABLE_KEY')
+ expect(ci_variable).to have_no_text('some_CI_variable')
- page.click_reveal_ci_variable_value_button
+ ci_variable.click_reveal_ci_variable_value_button
- expect(page).to have_text('some_CI_variable')
- end
+ expect(ci_variable).to have_text('some_CI_variable')
end
end
it 'user removes a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/394' do
- Page::Project::Settings::CICD.perform do |settings|
- settings.expand_ci_variables do |page|
- page.click_edit_ci_variable
- page.click_ci_variable_delete_button
+ Page::Project::Settings::CiVariables.perform do |ci_variable|
+ ci_variable.click_edit_ci_variable
+ ci_variable.click_ci_variable_delete_button
- expect(page).not_to have_text('VARIABLE_KEY')
- end
+ expect(ci_variable).to have_text('There are no variables yet', wait: 60)
end
end
@@ -50,11 +46,6 @@ module QA
ci_variable.masked = false
end
end
-
- def open_ci_cd_settings
- project.visit!
- Page::Project::Menu.perform(&:go_to_ci_cd_settings)
- end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 326647b25f7..8de739f1559 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:max_wait) { 30 }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
new file mode 100644
index 00000000000..153ccafaa20
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Verify' do
+ describe 'Run pipeline', :requires_admin, :skip_live_env do
+ # [TODO]: Developer to remove :requires_admin and :skip_live_env once FF is removed in https://gitlab.com/gitlab-org/gitlab/-/issues/229632
+
+ context 'with web only rule' do
+ let(:feature_flag) { :new_pipeline_form }
+ let(:job_name) { 'test_job' }
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'web-only-pipeline'
+ end
+ end
+
+ let!(:ci_file) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ #{job_name}:
+ tags:
+ - #{project.name}
+ script: echo 'OK'
+ only:
+ - web
+ YAML
+ }
+ ]
+ )
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(feature_flag) # [TODO]: Developer to remove when feature flag is removed
+ Flow::Login.sign_in
+ project.visit!
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ end
+
+ after do
+ Runtime::Feature.disable(feature_flag) # [TODO]: Developer to remove when feature flag is removed
+ end
+
+ it 'can trigger pipeline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/946' do
+ Page::Project::Pipeline::Index.perform do |index|
+ expect(index).not_to have_pipeline # should not auto trigger pipeline
+ index.click_run_pipeline_button
+ end
+
+ Page::Project::Pipeline::New.perform(&:click_run_pipeline_button)
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to have_job(job_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb
new file mode 100644
index 00000000000..39d5fbaba6b
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'faker'
+
+module QA
+ RSpec.describe 'Verify', :runner, :requires_admin do
+ # [TODO]: Developer to remove :requires_admin once FF is removed in follow up issue
+
+ describe "Trigger child pipeline with 'when:manual'" do
+ let(:feature_flag) { :ci_manual_bridges } # [TODO]: Developer to remove when feature flag is removed
+ let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" }
+
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project-with-pipeline'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = executor
+ runner.tags = [executor]
+ end
+ end
+
+ before do
+ Runtime::Feature.enable(feature_flag) # [TODO]: Developer to remove when feature flag is removed
+ Flow::Login.sign_in
+ add_ci_files
+ project.visit!
+ view_the_last_pipeline
+ end
+
+ after do
+ Runtime::Feature.disable(feature_flag) # [TODO]: Developer to remove when feature flag is removed
+ runner.remove_via_api!
+ end
+
+ it 'can trigger bridge job', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1049' do
+ Page::Project::Pipeline::Show.perform do |parent_pipeline|
+ expect(parent_pipeline).not_to have_child_pipeline
+
+ parent_pipeline.click_job_action('trigger')
+ Support::Waiter.wait_until { parent_pipeline.has_child_pipeline? }
+ parent_pipeline.expand_child_pipeline
+
+ expect(parent_pipeline).to have_build('child_build', status: nil)
+ end
+ end
+
+ private
+
+ def add_ci_files
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add parent and child pipelines CI files.'
+ commit.add_files(
+ [
+ child_ci_file,
+ parent_ci_file
+ ]
+ )
+ end
+ end
+
+ def view_the_last_pipeline
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+ end
+
+ def parent_ci_file
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ build:
+ stage: build
+ tags: ["#{executor}"]
+ script: echo build
+
+ trigger:
+ stage: test
+ when: manual
+ trigger:
+ include: '.child-pipeline.yml'
+
+ deploy:
+ stage: deploy
+ tags: ["#{executor}"]
+ script: echo deploy
+ YAML
+ }
+ end
+
+ def child_ci_file
+ {
+ file_path: '.child-pipeline.yml',
+ content: <<~YAML
+ child_build:
+ stage: build
+ tags: ["#{executor}"]
+ script: echo build
+ YAML
+ }
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index a296d60b27c..9ce87f353d0 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let!(:runner) do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
index f4edaaa84a8..5bfc88e45f2 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :docker, :runner do
+ RSpec.describe 'Verify', :runner do
describe 'Code coverage statistics' do
let(:simplecov) { '\(\d+.\d+\%\) covered' }
let(:executor) { "qa-runner-#{Time.now.to_i}" }
diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
index a617f3b3e29..4ca356c9b65 100644
--- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :docker, :orchestrated, :packages do
+ RSpec.describe 'Package', :orchestrated, :packages do
describe 'Maven Repository' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
index e97ede35610..817e146adfe 100644
--- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :docker, :orchestrated, :packages do
+ RSpec.describe 'Package', :orchestrated, :packages do
describe 'NPM registry' do
include Runtime::Fixtures
@@ -21,7 +21,7 @@ module QA
end
end
- it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/247281', type: :investigating } do
+ it 'publishes an npm package and then deletes it', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/944' do
uri = URI.parse(Runtime::Scenario.gitlab_address)
gitlab_host_with_port = "#{uri.host}:#{uri.port}"
gitlab_address_with_port = "#{uri.scheme}://#{uri.host}:#{uri.port}"
@@ -68,7 +68,7 @@ module QA
end
Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content("Package was removed")
+ expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 18eb52830a2..abac4f2b91d 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- RSpec.describe 'Release', :docker, :runner do
+ RSpec.describe 'Release', :runner do
describe 'Git clone using a deploy key' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
index 47a1b3b5670..ece45d093a7 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :docker, :runner, :reliable do
+ RSpec.describe 'Release', :runner, :reliable do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
index 9eb81244aa4..38cee0e62ca 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :docker, :runner, :reliable do
+ RSpec.describe 'Release', :runner, :reliable do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 53a1c8010af..6d31780f196 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -15,7 +15,7 @@ module QA
disable_optional_jobs(project)
end
- describe 'Auto DevOps support', :orchestrated, :kubernetes, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/230927', type: :stale } do
+ describe 'Auto DevOps support', :orchestrated, :kubernetes, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/251090', type: :stale } do
context 'when rbac is enabled' do
let(:cluster) { Service::KubernetesCluster.new.create! }
@@ -24,6 +24,8 @@ module QA
end
it 'runs auto devops', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/702' do
+ skip('Test requires tunnel: see https://gitlab.com/gitlab-org/gitlab/-/issues/251090')
+
Flow::Login.sign_in
# Set an application secret CI variable (prefixed with K8S_SECRET_)
diff --git a/qa/qa/specs/features/sanity/framework_spec.rb b/qa/qa/specs/features/sanity/framework_spec.rb
index 611c6c7b1ff..feec56478c0 100644
--- a/qa/qa/specs/features/sanity/framework_spec.rb
+++ b/qa/qa/specs/features/sanity/framework_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Framework sanity checks', :orchestrated, :framework do
+ RSpec.describe 'Framework sanity checks', :orchestrated, :framework do
describe 'Passing orchestrated example' do
it 'succeeds' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
index 6a3becf0ee5..d365819057e 100644
--- a/qa/qa/specs/helpers/quarantine.rb
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -20,7 +20,7 @@ module QA
Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
if example.metadata.key?(:only)
- skip('Test is not compatible with this environment') unless Runtime::Env.address_matches?(example.metadata[:only])
+ skip('Test is not compatible with this environment or pipeline') unless Runtime::Env.context_matches?(example.metadata[:only])
end
end
end
@@ -55,7 +55,7 @@ module QA
if quarantine_tag&.is_a?(Hash) && quarantine_tag&.key?(:only)
# If the :quarantine hash contains :only, we respect that.
# For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging.
- return unless Runtime::Env.address_matches?(quarantine_tag[:only])
+ return unless Runtime::Env.context_matches?(quarantine_tag[:only])
end
skip(quarantine_message(quarantine_tag))
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 08faacb6db3..5fc36b68e5c 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -40,7 +40,7 @@ module QA
return_response_or_raise(e)
end
- def put(url, payload)
+ def put(url, payload = nil)
RestClient::Request.execute(
method: :put,
url: url,
diff --git a/qa/qa/support/json_formatter.rb b/qa/qa/support/json_formatter.rb
index f6e40436ec8..0b805cd9eec 100644
--- a/qa/qa/support/json_formatter.rb
+++ b/qa/qa/support/json_formatter.rb
@@ -50,7 +50,8 @@ module QA
pending_message: example.execution_result.pending_message,
testcase: example.metadata[:testcase],
quarantine: example.metadata[:quarantine],
- screenshot: example.metadata[:screenshot]
+ screenshot: example.metadata[:screenshot],
+ ci_job_url: QA::Runtime::Env.ci_job_url
}
end
diff --git a/qa/qa/support/otp.rb b/qa/qa/support/otp.rb
index 0d7c394cf69..0a0dc64a726 100644
--- a/qa/qa/support/otp.rb
+++ b/qa/qa/support/otp.rb
@@ -13,11 +13,14 @@ module QA
# Fetches a fresh OTP and returns it only after rotp provides the same OTP twice
# An OTP is valid for 30 seconds so 70 attempts with 0.5 interval would ensure we complete 1 cycle
- Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5) do
+
+ QA::Runtime::Logger.debug("Fetching a fresh OTP...")
+ Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5, log: false) do
otps << @rotp.now
otps.size >= 3 && otps[-1] == otps[-2] && otps[-1] != otps[-3]
end
+ QA::Runtime::Logger.debug("Fetched OTP: #{otps.last}")
otps.last
end
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
index f28534e7c11..25dbb42cf6f 100644
--- a/qa/qa/support/retrier.rb
+++ b/qa/qa/support/retrier.rb
@@ -34,15 +34,17 @@ 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)
+ 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?
- 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}"
- QA::Runtime::Logger.debug(start_msg.join(' '))
+ if log
+ 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}"
+ QA::Runtime::Logger.debug(start_msg.join(' '))
+ end
result = nil
repeat_until(
@@ -51,7 +53,8 @@ module QA
reload_page: reload_page,
sleep_interval: sleep_interval,
raise_on_failure: raise_on_failure,
- retry_on_exception: retry_on_exception
+ retry_on_exception: retry_on_exception,
+ log: log
) do
result = yield
end
diff --git a/qa/qa/support/run.rb b/qa/qa/support/run.rb
new file mode 100644
index 00000000000..a91e7dfd2cb
--- /dev/null
+++ b/qa/qa/support/run.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'open3'
+
+module QA
+ module Support
+ module Run
+ include QA::Support::Repeater
+
+ CommandError = Class.new(StandardError)
+
+ Result = Struct.new(:command, :exitstatus, :response) do
+ alias_method :to_s, :response
+
+ def success?
+ exitstatus == 0 && !response.include?('Error encountered')
+ end
+ end
+
+ def run(command_str, env: [], max_attempts: 1, log_prefix: '')
+ command = [*env, command_str, '2>&1'].compact.join(' ')
+ result = nil
+
+ repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
+ Runtime::Logger.debug "#{log_prefix}pwd=[#{Dir.pwd}], command=[#{command}]"
+ output, status = Open3.capture2e(command)
+ output.chomp!
+ Runtime::Logger.debug "#{log_prefix}output=[#{output}], exitstatus=[#{status.exitstatus}]"
+
+ result = Result.new(command, status.exitstatus, output)
+
+ result.success?
+ end
+
+ unless result.success?
+ raise CommandError, "The command #{result.command} failed (#{result.exitstatus}) with the following output:\n#{result.response}"
+ end
+
+ result
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/ssh.rb b/qa/qa/support/ssh.rb
new file mode 100644
index 00000000000..a5e8e96cb6c
--- /dev/null
+++ b/qa/qa/support/ssh.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'tempfile'
+require 'etc'
+
+module QA
+ module Support
+ class SSH
+ include Scenario::Actable
+ include Support::Run
+
+ attr_accessor :known_hosts_file, :private_key_file, :key
+ attr_reader :uri
+
+ def initialize
+ @private_key_file = Tempfile.new("id_#{SecureRandom.hex(8)}")
+ @known_hosts_file = Tempfile.new("known_hosts_#{SecureRandom.hex(8)}")
+ end
+
+ def uri=(address)
+ @uri = URI(address)
+ end
+
+ def setup(env: nil)
+ File.binwrite(private_key_file, key.private_key)
+ File.chmod(0700, private_key_file)
+
+ keyscan_params = ['-H']
+ keyscan_params << "-p #{uri_port}" if uri_port
+ keyscan_params << uri.host
+
+ res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}", env: env, log_prefix: 'SSH: ')
+ return res.response unless res.success?
+
+ true
+ end
+
+ def delete
+ private_key_file.close(true)
+ known_hosts_file.close(true)
+ end
+
+ def reset_2fa_codes
+ ssh_params = [uri.host]
+ ssh_params << "-p #{uri_port}" if uri_port
+ ssh_params << "2fa_recovery_codes"
+
+ run("echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} #{git_user}@#{ssh_params.join(' ')}", log_prefix: 'SSH: ').to_s
+ end
+
+ private
+
+ def uri_port
+ uri.port && (uri.port != 80) ? uri.port : nil
+ end
+
+ def git_user
+ QA::Runtime::Env.running_in_ci? || [443, 80].include?(uri.port) ? 'git' : Etc.getlogin
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb
index 943d7d510df..ebc473a7d86 100644
--- a/qa/qa/support/wait_for_requests.rb
+++ b/qa/qa/support/wait_for_requests.rb
@@ -9,18 +9,14 @@ module QA
def wait_for_requests(skip_finished_loading_check: false)
Waiter.wait_until(log: false) do
- finished_all_ajax_requests? && finished_all_axios_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true)
+ finished_all_ajax_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true)
end
- end
-
- def finished_all_axios_requests?
- Capybara.page.evaluate_script('window.pendingRequests || 0').zero? # rubocop:disable Style/NumericPredicate
+ rescue Repeater::WaitExceededError
+ raise $!, 'Page did not fully load. This could be due to an unending async request or loading icon.'
end
def finished_all_ajax_requests?
- return true if Capybara.page.evaluate_script('typeof jQuery === "undefined"')
-
- Capybara.page.evaluate_script('jQuery.active').zero? # rubocop:disable Style/NumericPredicate
+ Capybara.page.evaluate_script('window.pendingRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate
end
def finished_loading?(wait: DEFAULT_MAX_WAIT_TIME)
diff --git a/qa/qa/tools/initialize_gitlab_auth.rb b/qa/qa/tools/initialize_gitlab_auth.rb
new file mode 100644
index 00000000000..b06ddcab040
--- /dev/null
+++ b/qa/qa/tools/initialize_gitlab_auth.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require_relative '../../qa'
+
+module QA
+ module Tools
+ # Task to set default password from Runtime::Env.default_password if not set already
+ # Also creates a personal access token
+ # @example
+ # $ bundle exec rake 'initialize_gitlab_auth[http://gitlab.test]'
+ class InitializeGitLabAuth
+ attr_reader :address
+
+ def initialize(address:)
+ @address = address
+ end
+
+ def run
+ Runtime::Scenario.define(:gitlab_address, address)
+
+ puts "Signing in and creating the default password for the root user if it's not set already..."
+ QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
+ Flow::Login.sign_in
+
+ puts "Creating an API scoped access token for the root user..."
+ puts "Token: #{Resource::PersonalAccessToken.fabricate!.access_token}"
+ end
+ end
+ end
+end
diff --git a/qa/spec/factory/resource/user_spec.rb b/qa/spec/factory/resource/user_spec.rb
index d59ee24c758..1adf3799b0e 100644
--- a/qa/spec/factory/resource/user_spec.rb
+++ b/qa/spec/factory/resource/user_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::User do
+RSpec.describe QA::Resource::User do
describe "#fabricate_via_api!" do
response = Struct.new(:code, :body)
diff --git a/qa/spec/git/location_spec.rb b/qa/spec/git/location_spec.rb
index 0c57291666f..ee714206e4f 100644
--- a/qa/spec/git/location_spec.rb
+++ b/qa/spec/git/location_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Git::Location do
+RSpec.describe QA::Git::Location do
describe '.new' do
context 'when URI starts with ssh://' do
context 'when URI has port' do
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 8355c77f493..02bb7783c28 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -1,12 +1,21 @@
# frozen_string_literal: true
-describe QA::Git::Repository do
+RSpec.describe QA::Git::Repository do
include Helpers::StubENV
shared_context 'unresolvable git directory' do
let(:repo_uri) { 'http://foo/bar.git' }
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
- let(:repository) { described_class.new.tap { |r| r.uri = repo_uri } }
+ let(:env_vars) { [%q{HOME="temp"}] }
+ let(:extra_env_vars) { [] }
+ let(:run_params) { { env: env_vars + extra_env_vars, log_prefix: "Git: " } }
+ let(:repository) do
+ described_class.new.tap do |r|
+ r.uri = repo_uri
+ r.env_vars = env_vars
+ end
+ end
+
let(:tmp_git_dir) { Dir.mktmpdir }
let(:tmp_netrc_dir) { Dir.mktmpdir }
@@ -28,14 +37,13 @@ describe QA::Git::Repository do
end
shared_examples 'command with retries' do
- let(:extra_args) { {} }
let(:result_output) { +'Command successful' }
let(:result) { described_class::Result.new(any_args, 0, result_output) }
let(:command_return) { result_output }
context 'when command is successful' do
it 'returns the #run command Result output' do
- expect(repository).to receive(:run).with(command, extra_args.merge(max_attempts: 3)).and_return(result)
+ expect(repository).to receive(:run).with(command, run_params.merge(max_attempts: 3)).and_return(result)
expect(call_method).to eq(command_return)
end
@@ -52,10 +60,10 @@ describe QA::Git::Repository do
end
context 'and retried command is not successful after 3 attempts' do
- it 'raises a RepositoryCommandError exception' do
+ it 'raises a CommandError exception' do
expect(Open3).to receive(:capture2e).and_return([+'FAILURE', double(exitstatus: 42)]).exactly(3).times
- expect { call_method }.to raise_error(described_class::RepositoryCommandError, /The command .* failed \(42\) with the following output:\nFAILURE/)
+ expect { call_method }.to raise_error(QA::Support::Run::CommandError, /The command .* failed \(42\) with the following output:\nFAILURE/)
end
end
end
@@ -117,12 +125,72 @@ describe QA::Git::Repository do
let(:call_method) { repository.push_changes(branch) }
end
end
+
+ context 'with push options' do
+ let(:command) { "git push #{push_options} #{repo_uri_with_credentials} #{branch}" }
+
+ context 'when set to create a merge request' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.create' }
+ let(:call_method) { repository.push_changes(push_options: { create: true }) }
+ end
+ end
+
+ context 'when set to merge when pipeline succeeds' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.merge_when_pipeline_succeeds' }
+ let(:call_method) { repository.push_changes(push_options: { merge_when_pipeline_succeeds: true }) }
+ end
+ end
+
+ context 'when set to remove source branch' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.remove_source_branch' }
+ let(:call_method) { repository.push_changes(push_options: { remove_source_branch: true }) }
+ end
+ end
+
+ context 'when title is given' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.title="Is A Title"' }
+ let(:call_method) { repository.push_changes(push_options: { title: 'Is A Title' }) }
+ end
+ end
+
+ context 'when description is given' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.description="Is A Description"' }
+ let(:call_method) { repository.push_changes(push_options: { description: 'Is A Description' }) }
+ end
+ end
+
+ context 'when target branch is given' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.target="is-a-target-branch"' }
+ let(:call_method) { repository.push_changes(push_options: { target: 'is-a-target-branch' }) }
+ end
+ end
+
+ context 'when a label is given' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.label="is-a-label"' }
+ let(:call_method) { repository.push_changes(push_options: { label: ['is-a-label'] }) }
+ end
+ end
+
+ context 'when two labels are given' do
+ it_behaves_like 'command with retries' do
+ let(:push_options) { '-o merge_request.label="is-a-label" -o merge_request.label="is-another-label"' }
+ let(:call_method) { repository.push_changes(push_options: { label: %w[is-a-label is-another-label] }) }
+ end
+ end
+ end
end
describe '#git_protocol=' do
[0, 1, 2].each do |version|
it "configures git to use protocol version #{version}" do
- expect(repository).to receive(:run).with("git config protocol.version #{version}")
+ expect(repository).to receive(:run).with("git config protocol.version #{version}", run_params.merge(max_attempts: 1))
repository.git_protocol = version
end
@@ -140,7 +208,7 @@ describe QA::Git::Repository do
let(:command) { "git ls-remote #{repo_uri_with_credentials}" }
let(:result_output) { +'packet: git< version 2' }
let(:command_return) { '2' }
- let(:extra_args) { { env: "GIT_TRACE_PACKET=1" } }
+ let(:extra_env_vars) { ["GIT_TRACE_PACKET=1"] }
end
it "reports the detected version" do
diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb
index 0cbb0a2b12e..52345876149 100644
--- a/qa/spec/page/base_spec.rb
+++ b/qa/spec/page/base_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Page::Base do
+RSpec.describe QA::Page::Base do
describe 'page helpers' do
it 'exposes helpful page helpers' do
expect(subject).to respond_to :refresh, :wait_until, :scroll_to
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index 3f64743ffac..fbf58b5e18a 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Page::Element do
+RSpec.describe QA::Page::Element do
describe '#selector' do
it 'transforms element name into QA-specific selector' do
expect(described_class.new(:sign_in_button).selector)
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index f2ce9fb2cf8..df3447770be 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -3,7 +3,7 @@
require 'capybara/dsl'
require 'logger'
-describe QA::Support::Page::Logging do
+RSpec.describe QA::Support::Page::Logging do
let(:page) { double.as_null_object }
before do
diff --git a/qa/spec/page/validator_spec.rb b/qa/spec/page/validator_spec.rb
index c727cfb686e..cfb36052294 100644
--- a/qa/spec/page/validator_spec.rb
+++ b/qa/spec/page/validator_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Page::Validator do
+RSpec.describe QA::Page::Validator do
describe '#constants' do
subject do
described_class.new(QA::Page::Project)
diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb
index 3cb64dcd9c2..3342b387ed1 100644
--- a/qa/spec/page/view_spec.rb
+++ b/qa/spec/page/view_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Page::View do
+RSpec.describe QA::Page::View do
let(:element) do
double('element', name: :something, pattern: /some element/)
end
diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb
index eb2bdd1be64..69a95c92332 100644
--- a/qa/spec/resource/api_fabricator_spec.rb
+++ b/qa/spec/resource/api_fabricator_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::ApiFabricator do
+RSpec.describe QA::Resource::ApiFabricator do
let(:resource_without_api_support) do
Class.new do
def self.name
diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb
index b23de19e1f8..c0bedf794be 100644
--- a/qa/spec/resource/base_spec.rb
+++ b/qa/spec/resource/base_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::Base do
+RSpec.describe QA::Resource::Base do
include Helpers::StubENV
let(:resource) { spy('resource') }
diff --git a/qa/spec/resource/events/base_spec.rb b/qa/spec/resource/events/base_spec.rb
index 9cdf4785092..4df30a970fc 100644
--- a/qa/spec/resource/events/base_spec.rb
+++ b/qa/spec/resource/events/base_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::Events::Base do
+RSpec.describe QA::Resource::Events::Base do
let(:resource) do
Class.new(QA::Resource::Base) do
def api_get_path
diff --git a/qa/spec/resource/events/project_spec.rb b/qa/spec/resource/events/project_spec.rb
index 98da87906fa..88d50749a0a 100644
--- a/qa/spec/resource/events/project_spec.rb
+++ b/qa/spec/resource/events/project_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::Events::Project do
+RSpec.describe QA::Resource::Events::Project do
let(:resource) do
Class.new(QA::Resource::Base) do
def api_get_path
diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb
index 2f9e4958ae1..2b9c90b3dac 100644
--- a/qa/spec/resource/repository/push_spec.rb
+++ b/qa/spec/resource/repository/push_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::Repository::Push do
+RSpec.describe QA::Resource::Repository::Push do
describe '.files=' do
let(:files) do
[
diff --git a/qa/spec/resource/ssh_key_spec.rb b/qa/spec/resource/ssh_key_spec.rb
index b2b5ec070e1..fd0fda3c1b8 100644
--- a/qa/spec/resource/ssh_key_spec.rb
+++ b/qa/spec/resource/ssh_key_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::SSHKey do
+RSpec.describe QA::Resource::SSHKey do
describe '#key' do
it 'generates a default key' do
expect(subject.key).to be_a(QA::Runtime::Key::RSA)
diff --git a/qa/spec/resource/user_spec.rb b/qa/spec/resource/user_spec.rb
index 5845f7996a3..e7397d9c0bf 100644
--- a/qa/spec/resource/user_spec.rb
+++ b/qa/spec/resource/user_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Resource::User do
+RSpec.describe QA::Resource::User do
let(:api_resource) do
{
name: "GitLab QA",
diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb
index 6f7020d6595..dd139fda980 100644
--- a/qa/spec/runtime/api/client_spec.rb
+++ b/qa/spec/runtime/api/client_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::API::Client do
+RSpec.describe QA::Runtime::API::Client do
include Helpers::StubENV
describe 'initialization' do
diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb
index 8354eff6234..93de2f4a87e 100644
--- a/qa/spec/runtime/api/request_spec.rb
+++ b/qa/spec/runtime/api/request_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::API::Request do
+RSpec.describe QA::Runtime::API::Request do
let(:client) { QA::Runtime::API::Client.new('http://example.com') }
let(:request) { described_class.new(client, '/users') }
diff --git a/qa/spec/runtime/application_settings_spec.rb b/qa/spec/runtime/application_settings_spec.rb
index e48214b22e6..5c4947f6f70 100644
--- a/qa/spec/runtime/application_settings_spec.rb
+++ b/qa/spec/runtime/application_settings_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::ApplicationSettings do
+RSpec.describe QA::Runtime::ApplicationSettings do
let(:api_client) { double('QA::Runtime::API::Client') }
let(:request) { Struct.new(:url).new('http://api') }
let(:get_response) { Struct.new(:body).new("{}") }
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index 0cfb9a70c88..3396ae6f0b8 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Env do
+RSpec.describe QA::Runtime::Env do
include Helpers::StubENV
shared_examples 'boolean method' do |**kwargs|
@@ -341,7 +341,7 @@ describe QA::Runtime::Env do
end
end
- describe '.address_matches?' do
+ describe '.context_matches?' do
it 'returns true when url has .com' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
@@ -364,24 +364,24 @@ describe QA::Runtime::Env do
it 'matches multiple subdomains' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
- expect(described_class.address_matches?(subdomain: [:release, :staging])).to be_truthy
- expect(described_class.address_matches?(:production, subdomain: [:release, :staging])).to be_truthy
+ expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy
+ expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy
end
it 'matches :production' do
QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/")
- expect(described_class.address_matches?(:production)).to be_truthy
+ expect(described_class.context_matches?(:production)).to be_truthy
end
it 'doesnt match with mismatching switches' do
QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test')
aggregate_failures do
- expect(described_class.address_matches?(tld: '.net')).to be_falsey
- expect(described_class.address_matches?(:production)).to be_falsey
- expect(described_class.address_matches?(subdomain: [:staging])).to be_falsey
- expect(described_class.address_matches?(domain: 'example')).to be_falsey
+ expect(described_class.context_matches?(tld: '.net')).to be_falsey
+ expect(described_class.context_matches?(:production)).to be_falsey
+ expect(described_class.context_matches?(subdomain: [:staging])).to be_falsey
+ expect(described_class.context_matches?(domain: 'example')).to be_falsey
end
end
end
@@ -389,7 +389,7 @@ describe QA::Runtime::Env do
it 'returns false for mismatching' do
QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com")
- expect(described_class.address_matches?(:production)).to be_falsey
+ expect(described_class.context_matches?(:production)).to be_falsey
end
end
end
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
index db3c2f65963..39c20dd3070 100644
--- a/qa/spec/runtime/feature_spec.rb
+++ b/qa/spec/runtime/feature_spec.rb
@@ -1,87 +1,215 @@
# frozen_string_literal: true
-describe QA::Runtime::Feature do
+RSpec.describe QA::Runtime::Feature do
let(:api_client) { double('QA::Runtime::API::Client') }
let(:request) { Struct.new(:url).new('http://api') }
let(:response_post) { Struct.new(:code).new(201) }
- let(:response_get) { Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "on" }]') }
before do
allow(described_class).to receive(:api_client).and_return(api_client)
end
- describe '.enable' do
- it 'enables a feature flag' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features/a-flag")
- .and_return(request)
- expect(described_class)
- .to receive(:post)
- .with(request.url, { value: true })
- .and_return(response_post)
-
- subject.enable('a-flag')
- end
+ where(:feature_flag) do
+ ['a_flag', :a_flag]
end
- describe '.enable_and_verify' do
- it 'enables a feature flag' do
- allow(described_class).to receive(:get).and_return(response_get)
+ with_them do
+ shared_examples 'enables a feature flag' do
+ it 'enables a feature flag for a scope' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features/a-flag").and_return(request)
- expect(described_class).to receive(:post)
- .with(request.url, { value: true }).and_return(response_post)
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: true, scope => actor_name }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::Logger).to receive(:info).with("Enabling feature: a_flag for scope \"#{scope}: #{actor_name}\"")
+ expect(QA::Runtime::Logger).to receive(:info).with("Successfully enabled and verified feature flag: a_flag")
- subject.enable_and_verify('a-flag')
+ described_class.enable(feature_flag, scope => actor)
+ end
end
- end
- describe '.disable' do
- it 'disables a feature flag' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features/a-flag")
- .and_return(request)
- expect(described_class)
- .to receive(:post)
- .with(request.url, { value: false })
- .and_return(response_post)
-
- subject.disable('a-flag')
+ shared_examples 'disables a feature flag' do
+ it 'disables a feature flag for a scope' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]'))
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: false, scope => actor_name }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+ expect(QA::Runtime::Logger).to receive(:info).with("Disabling feature: a_flag for scope \"#{scope}: #{actor_name}\"")
+ expect(QA::Runtime::Logger).to receive(:info).with("Successfully disabled and verified feature flag: a_flag")
+
+ described_class.disable(feature_flag, scope => actor )
+ end
end
- end
- describe '.disable_and_verify' do
- it 'disables a feature flag' do
- allow(described_class).to receive(:get)
- .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "off" }]'))
+ shared_examples 'checks a feature flag' do
+ context 'when the flag is enabled for a scope' do
+ it 'returns the feature flag state' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }])))
+
+ expect(described_class.enabled?(feature_flag, scope => actor)).to be_truthy
+ end
+ end
+ end
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features/a-flag").and_return(request)
- expect(described_class).to receive(:post)
- .with(request.url, { value: false }).and_return(response_post)
- expect(QA::Runtime::API::Request).to receive(:new)
- .with(api_client, "/features").and_return(request)
+ describe '.enable' do
+ it 'enables a feature flag' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- subject.disable_and_verify('a-flag')
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: true }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ described_class.enable(feature_flag)
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:username).new(actor_name) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'enables a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ end
+ end
end
- end
- describe '.enabled?' do
- it 'returns a feature flag state' do
- expect(QA::Runtime::API::Request)
- .to receive(:new)
- .with(api_client, "/features")
- .and_return(request)
- expect(described_class)
- .to receive(:get)
- .and_return(response_get)
-
- expect(subject.enabled?('a-flag')).to be_truthy
+ describe '.disable' do
+ it 'disables a feature flag' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "off" }]'))
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a_flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: false }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ described_class.disable(feature_flag)
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path).new(actor_name) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:username).new(actor_name) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'disables a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ end
+ end
+ end
+
+ describe '.enabled?' do
+ it 'returns a feature flag state' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
+
+ expect(described_class.enabled?(feature_flag)).to be_truthy
+ end
+
+ context 'when a project scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :project }
+ let(:actor_name) { 'group-name/project-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 270) }
+ let(:gates) { %q([{"key": "actors", "value": ["Project:270"]}]) }
+ end
+ end
+
+ context 'when a group scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :group }
+ let(:actor_name) { 'group-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 33) }
+ let(:gates) { %q([{"key": "actors", "value": ["Group:33"]}]) }
+ end
+ end
+
+ context 'when a user scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :user }
+ let(:actor_name) { 'user-name' }
+ let(:actor) { Struct.new(:full_path, :id).new(actor_name, 13) }
+ let(:gates) { %q([{"key": "actors", "value": ["User:13"]}]) }
+ end
+ end
+
+ context 'when a feature group scope is provided' do
+ it_behaves_like 'checks a feature flag' do
+ let(:scope) { :feature_group }
+ let(:actor_name) { 'foo' }
+ let(:actor) { "foo" }
+ let(:gates) { %q([{"key": "groups", "value": ["foo"]}]) }
+ end
+ end
end
end
end
diff --git a/qa/spec/runtime/key/ecdsa_spec.rb b/qa/spec/runtime/key/ecdsa_spec.rb
index 3f9718e62c5..499233df618 100644
--- a/qa/spec/runtime/key/ecdsa_spec.rb
+++ b/qa/spec/runtime/key/ecdsa_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Key::ECDSA do
+RSpec.describe QA::Runtime::Key::ECDSA do
describe '#public_key' do
[256, 384, 521].each do |bits|
it "generates a public #{bits}-bits ECDSA key" do
diff --git a/qa/spec/runtime/key/ed25519_spec.rb b/qa/spec/runtime/key/ed25519_spec.rb
index 08f232260af..e63c7f5deae 100644
--- a/qa/spec/runtime/key/ed25519_spec.rb
+++ b/qa/spec/runtime/key/ed25519_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Key::ED25519 do
+RSpec.describe QA::Runtime::Key::ED25519 do
describe '#public_key' do
subject { described_class.new.public_key }
diff --git a/qa/spec/runtime/key/rsa_spec.rb b/qa/spec/runtime/key/rsa_spec.rb
index fcb52f541bf..5b5d8a13fa1 100644
--- a/qa/spec/runtime/key/rsa_spec.rb
+++ b/qa/spec/runtime/key/rsa_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Key::RSA do
+RSpec.describe QA::Runtime::Key::RSA do
describe '#public_key' do
subject { described_class.new.public_key }
diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb
index 44be3381bff..a888bf1452b 100644
--- a/qa/spec/runtime/logger_spec.rb
+++ b/qa/spec/runtime/logger_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Logger do
+RSpec.describe QA::Runtime::Logger do
before do
logger = Logger.new $stdout
logger.level = ::Logger::DEBUG
diff --git a/qa/spec/runtime/namespace_spec.rb b/qa/spec/runtime/namespace_spec.rb
index d24fa509f30..92836862864 100644
--- a/qa/spec/runtime/namespace_spec.rb
+++ b/qa/spec/runtime/namespace_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Namespace do
+RSpec.describe QA::Runtime::Namespace do
include Helpers::StubENV
describe '.name' do
diff --git a/qa/spec/runtime/release_spec.rb b/qa/spec/runtime/release_spec.rb
index b5a7dd5269d..b4e278fb546 100644
--- a/qa/spec/runtime/release_spec.rb
+++ b/qa/spec/runtime/release_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Release do
+RSpec.describe QA::Runtime::Release do
context 'when release version has extension strategy' do
let(:strategy) { spy('strategy') }
diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb
index 30ada4529ed..175973b6795 100644
--- a/qa/spec/runtime/scenario_spec.rb
+++ b/qa/spec/runtime/scenario_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Runtime::Scenario do
+RSpec.describe QA::Runtime::Scenario do
subject do
Module.new.extend(described_class)
end
diff --git a/qa/spec/scenario/actable_spec.rb b/qa/spec/scenario/actable_spec.rb
index 589d0c61993..36e9f3de961 100644
--- a/qa/spec/scenario/actable_spec.rb
+++ b/qa/spec/scenario/actable_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Actable do
+RSpec.describe QA::Scenario::Actable do
subject do
Class.new do
include QA::Scenario::Actable
diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb
index e8accb45518..8a96e9bebbf 100644
--- a/qa/spec/scenario/bootable_spec.rb
+++ b/qa/spec/scenario/bootable_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Bootable do
+RSpec.describe QA::Scenario::Bootable do
subject do
Class.new(QA::Scenario::Template)
.include(described_class)
diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb
index 65793734548..f07d817ea16 100644
--- a/qa/spec/scenario/template_spec.rb
+++ b/qa/spec/scenario/template_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Template do
+RSpec.describe QA::Scenario::Template do
let(:feature) { spy('Runtime::Feature') }
let(:release) { spy('Runtime::Release') }
diff --git a/qa/spec/scenario/test/instance/airgapped_spec.rb b/qa/spec/scenario/test/instance/airgapped_spec.rb
index 0c4167eafff..5e319ba4bbb 100644
--- a/qa/spec/scenario/test/instance/airgapped_spec.rb
+++ b/qa/spec/scenario/test/instance/airgapped_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Instance::Airgapped do
+RSpec.describe QA::Scenario::Test::Instance::Airgapped do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
end
diff --git a/qa/spec/scenario/test/instance/all_spec.rb b/qa/spec/scenario/test/instance/all_spec.rb
index 8acd56914c5..875df9a32f5 100644
--- a/qa/spec/scenario/test/instance/all_spec.rb
+++ b/qa/spec/scenario/test/instance/all_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Instance::All do
+RSpec.describe QA::Scenario::Test::Instance::All do
it_behaves_like 'a QA scenario class'
end
diff --git a/qa/spec/scenario/test/instance/smoke_spec.rb b/qa/spec/scenario/test/instance/smoke_spec.rb
index 6cc71699be9..09d0df2c479 100644
--- a/qa/spec/scenario/test/instance/smoke_spec.rb
+++ b/qa/spec/scenario/test/instance/smoke_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Instance::Smoke do
+RSpec.describe QA::Scenario::Test::Instance::Smoke do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:smoke] }
end
diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb
index b2d577bd552..b68b06a7b9f 100644
--- a/qa/spec/scenario/test/integration/github_spec.rb
+++ b/qa/spec/scenario/test/integration/github_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::Github do
+RSpec.describe QA::Scenario::Test::Integration::Github do
describe '#perform' do
let(:env) { spy('Runtime::Env') }
diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb
index 15f15b2e643..20e860d3e4b 100644
--- a/qa/spec/scenario/test/integration/instance_saml_spec.rb
+++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::InstanceSAML do
+RSpec.describe QA::Scenario::Test::Integration::InstanceSAML do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:instance_saml] }
diff --git a/qa/spec/scenario/test/integration/kubernetes_spec.rb b/qa/spec/scenario/test/integration/kubernetes_spec.rb
index 51ee7b9acff..d5885b97343 100644
--- a/qa/spec/scenario/test/integration/kubernetes_spec.rb
+++ b/qa/spec/scenario/test/integration/kubernetes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::Kubernetes do
+RSpec.describe QA::Scenario::Test::Integration::Kubernetes do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:kubernetes] }
diff --git a/qa/spec/scenario/test/integration/ldap_spec.rb b/qa/spec/scenario/test/integration/ldap_spec.rb
index c493cde6c7a..c53302d9bd3 100644
--- a/qa/spec/scenario/test/integration/ldap_spec.rb
+++ b/qa/spec/scenario/test/integration/ldap_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::LDAPNoTLS do
+RSpec.describe QA::Scenario::Test::Integration::LDAPNoTLS do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:ldap_no_tls] }
@@ -8,7 +8,7 @@ describe QA::Scenario::Test::Integration::LDAPNoTLS do
end
end
-describe QA::Scenario::Test::Integration::LDAPNoServer do
+RSpec.describe QA::Scenario::Test::Integration::LDAPNoServer do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:ldap_no_server] }
@@ -16,7 +16,7 @@ describe QA::Scenario::Test::Integration::LDAPNoServer do
end
end
-describe QA::Scenario::Test::Integration::LDAPTLS do
+RSpec.describe QA::Scenario::Test::Integration::LDAPTLS do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:ldap_tls] }
diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb
index 7e4eb6284e8..9532ec35b95 100644
--- a/qa/spec/scenario/test/integration/mattermost_spec.rb
+++ b/qa/spec/scenario/test/integration/mattermost_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::Mattermost do
+RSpec.describe QA::Scenario::Test::Integration::Mattermost do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:args) { %w[gitlab_address mattermost_address] }
diff --git a/qa/spec/scenario/test/integration/object_storage_spec.rb b/qa/spec/scenario/test/integration/object_storage_spec.rb
index 8b4367bee32..235dd495687 100644
--- a/qa/spec/scenario/test/integration/object_storage_spec.rb
+++ b/qa/spec/scenario/test/integration/object_storage_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Integration::ObjectStorage do
+RSpec.describe QA::Scenario::Test::Integration::ObjectStorage do
describe '#perform' do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:object_storage] }
diff --git a/qa/spec/scenario/test/sanity/framework_spec.rb b/qa/spec/scenario/test/sanity/framework_spec.rb
index a63c59e2995..5ae8b123ec2 100644
--- a/qa/spec/scenario/test/sanity/framework_spec.rb
+++ b/qa/spec/scenario/test/sanity/framework_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Sanity::Framework do
+RSpec.describe QA::Scenario::Test::Sanity::Framework do
it_behaves_like 'a QA scenario class' do
let(:tags) { [:framework] }
end
diff --git a/qa/spec/scenario/test/sanity/selectors_spec.rb b/qa/spec/scenario/test/sanity/selectors_spec.rb
index e18babaed63..2a68dd23219 100644
--- a/qa/spec/scenario/test/sanity/selectors_spec.rb
+++ b/qa/spec/scenario/test/sanity/selectors_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Scenario::Test::Sanity::Selectors do
+RSpec.describe QA::Scenario::Test::Sanity::Selectors do
let(:validator) { spy('validator') }
before do
diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb
index 9686a9771c4..41bc3eadff4 100644
--- a/qa/spec/specs/helpers/quarantine_spec.rb
+++ b/qa/spec/specs/helpers/quarantine_spec.rb
@@ -36,7 +36,9 @@ RSpec.configure do |c|
end
end
-describe QA::Specs::Helpers::Quarantine do
+RSpec.describe QA::Specs::Helpers::Quarantine do
+ include Helpers::StubENV
+
describe '.skip_or_run_quarantined_contexts' do
context 'with no tag focused' do
before do
@@ -312,7 +314,7 @@ describe QA::Specs::Helpers::Quarantine do
end
end
- describe 'running against specific environments' do
+ describe 'running against specific environments or pipelines' do
before do
QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com')
described_class.configure_rspec
@@ -400,5 +402,70 @@ describe QA::Specs::Helpers::Quarantine do
expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/)
end
+
+ context 'with pipeline constraints' do
+ context 'without CI_PROJECT_NAME set' do
+ before do
+ stub_env('CI_PROJECT_NAME', nil)
+ described_class.configure_rspec
+ end
+
+ it 'runs on any pipeline' do
+ group = describe_successfully do
+ it('runs given a single named pipeline', only: { pipeline: :nightly } ) {}
+ it('runs given an array of pipelines', only: { pipeline: [:canary, :not_nightly] }) {}
+ end
+
+ aggregate_failures do
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ expect(group.examples[1].execution_result.status).to eq(:passed)
+ end
+ end
+ end
+
+ context 'when a pipeline triggered from master runs in gitlab-qa' do
+ before do
+ stub_env('CI_PROJECT_NAME', 'gitlab-qa')
+ described_class.configure_rspec
+ end
+
+ it 'runs on master pipelines' do
+ group = describe_successfully do
+ it('runs on master pipeline given a single pipeline', only: { pipeline: :master } ) {}
+ it('runs in master given an array of pipelines', only: { pipeline: [:canary, :master] }) {}
+ it('does not run in non-master pipelines', only: { pipeline: [:nightly, :not_nightly, :not_master] } ) {}
+ end
+
+ aggregate_failures do
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ expect(group.examples[1].execution_result.status).to eq(:passed)
+ expect(group.examples[2].execution_result.status).to eq(:pending)
+ end
+ end
+ end
+
+ context 'with CI_PROJECT_NAME set' do
+ before do
+ stub_env('CI_PROJECT_NAME', 'NIGHTLY')
+ described_class.configure_rspec
+ end
+
+ it 'runs on designated pipeline' do
+ group = describe_successfully do
+ it('runs on nightly', only: { pipeline: :nightly } ) {}
+ it('does not run in not_nightly', only: { pipeline: :not_nightly } ) {}
+ it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {}
+ it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {}
+ end
+
+ aggregate_failures do
+ expect(group.examples[0].execution_result.status).to eq(:passed)
+ expect(group.examples[1].execution_result.status).to eq(:pending)
+ expect(group.examples[2].execution_result.status).to eq(:passed)
+ expect(group.examples[3].execution_result.status).to eq(:pending)
+ end
+ end
+ end
+ end
end
end
diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb
index 67d94a1f648..c2d28bf81fb 100644
--- a/qa/spec/specs/parallel_runner_spec.rb
+++ b/qa/spec/specs/parallel_runner_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-describe QA::Specs::ParallelRunner do
+RSpec.describe QA::Specs::ParallelRunner do
include Helpers::StubENV
before do
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 361588fa14f..8171cfcb3e6 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -2,7 +2,7 @@
require 'active_support/core_ext/hash'
-describe QA::Specs::Runner do
+RSpec.describe QA::Specs::Runner do
shared_examples 'excludes orchestrated' do
it 'excludes the orchestrated tag and includes default args' do
expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS])
diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb
index b5d5058ef49..18ccbf250cb 100644
--- a/qa/spec/support/repeater_spec.rb
+++ b/qa/spec/support/repeater_spec.rb
@@ -4,7 +4,7 @@ require 'logger'
require 'timecop'
require 'active_support/core_ext/integer/time'
-describe QA::Support::Repeater do
+RSpec.describe QA::Support::Repeater do
before do
logger = ::Logger.new $stdout
logger.level = ::Logger::DEBUG
diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb
index ef1d53e6b65..6f052519516 100644
--- a/qa/spec/support/retrier_spec.rb
+++ b/qa/spec/support/retrier_spec.rb
@@ -3,7 +3,7 @@
require 'logger'
require 'timecop'
-describe QA::Support::Retrier do
+RSpec.describe QA::Support::Retrier do
before do
logger = ::Logger.new $stdout
logger.level = ::Logger::DEBUG
diff --git a/qa/spec/support/run_spec.rb b/qa/spec/support/run_spec.rb
new file mode 100644
index 00000000000..62eed71012e
--- /dev/null
+++ b/qa/spec/support/run_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.describe QA::Support::Run do
+ let(:class_instance) { (Class.new { include QA::Support::Run }).new }
+ let(:response) { 'successful response' }
+ let(:command) { 'some command' }
+ let(:expected_result) { described_class::Result.new("#{command} 2>&1", 0, response) }
+
+ it 'runs successfully' do
+ expect(Open3).to receive(:capture2e).and_return([+response, double(exitstatus: 0)])
+
+ expect(class_instance.run(command)).to eq(expected_result)
+ end
+
+ it 'retries twice and succeeds the third time' do
+ allow(Open3).to receive(:capture2e).and_return([+'', double(exitstatus: 1)]).twice
+ allow(Open3).to receive(:capture2e).and_return([+response, double(exitstatus: 0)])
+
+ expect(class_instance.run(command)).to eq(expected_result)
+ end
+
+ it 'raises an exception on 3rd failure' do
+ allow(Open3).to receive(:capture2e).and_return([+'FAILURE', double(exitstatus: 1)]).thrice
+
+ expect { class_instance.run(command) }.to raise_error(QA::Support::Run::CommandError, /The command .* failed \(1\) with the following output:\nFAILURE/)
+ end
+end
diff --git a/qa/spec/support/ssh_spec.rb b/qa/spec/support/ssh_spec.rb
new file mode 100644
index 00000000000..f4d382f8adc
--- /dev/null
+++ b/qa/spec/support/ssh_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+RSpec.describe QA::Support::SSH do
+ let(:key) { Struct.new(:private_key).new('private_key') }
+ let(:known_hosts_file) { Tempfile.new('known_hosts_file') }
+ let(:private_key_file) { Tempfile.new('private_key_file') }
+ let(:result) { QA::Support::Run::Result.new('', 0, '') }
+
+ let(:ssh) do
+ described_class.new.tap do |ssh|
+ ssh.uri = uri
+ ssh.key = key
+ ssh.private_key_file = private_key_file
+ ssh.known_hosts_file = known_hosts_file
+ end
+ end
+
+ shared_examples 'providing correct ports' do
+ context 'when no port specified in uri' do
+ let(:uri) { 'http://foo.com' }
+
+ it 'does not provide port in ssh command' do
+ expect(ssh).to receive(:run).with(expected_ssh_command_no_port, any_args).and_return(result)
+
+ call_method
+ end
+ end
+
+ context 'when port 80 specified in uri' do
+ let(:uri) { 'http://foo.com:80' }
+
+ it 'does not provide port in ssh command' do
+ expect(ssh).to receive(:run).with(expected_ssh_command_no_port, any_args).and_return(result)
+
+ call_method
+ end
+ end
+
+ context 'when other port is specified in uri' do
+ let(:port) { 1234 }
+ let(:uri) { "http://foo.com:#{port}" }
+
+ it "provides other port in ssh command" do
+ expect(ssh).to receive(:run).with(expected_ssh_command_port, any_args).and_return(result)
+
+ call_method
+ end
+ end
+ end
+
+ describe '#setup' do
+ let(:expected_ssh_command_no_port) { "ssh-keyscan -H foo.com >> #{known_hosts_file.path}" }
+ let(:expected_ssh_command_port) { "ssh-keyscan -H -p #{port} foo.com >> #{known_hosts_file.path}" }
+ let(:call_method) { ssh.setup }
+
+ before do
+ allow(File).to receive(:binwrite).with(private_key_file, key.private_key)
+ allow(File).to receive(:chmod).with(0700, private_key_file)
+ end
+
+ it_behaves_like 'providing correct ports'
+ end
+
+ describe '#reset_2fa_codes' do
+ let(:expected_ssh_command_no_port) { "echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} git@foo.com 2fa_recovery_codes" }
+ let(:expected_ssh_command_port) { "echo yes | ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path} git@foo.com -p #{port} 2fa_recovery_codes" }
+ let(:call_method) { ssh.reset_2fa_codes }
+
+ before do
+ allow(ssh).to receive(:git_user).and_return('git')
+ end
+
+ it_behaves_like 'providing correct ports'
+ end
+
+ describe '#git_user' do
+ context 'when running on CI' do
+ let(:uri) { 'http://gitlab.com' }
+
+ before do
+ allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(true)
+ end
+
+ it 'returns git user' do
+ expect(ssh.send(:git_user)).to eq('git')
+ end
+ end
+
+ context 'when running against environment on a port other than 80 or 443' do
+ let(:uri) { 'http://localhost:3000' }
+
+ before do
+ allow(Etc).to receive(:getlogin).and_return('dummy_username')
+ allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(false)
+ end
+
+ it 'returns the local user' do
+ expect(ssh.send(:git_user)).to eq('dummy_username')
+ end
+ end
+
+ context 'when running against environment on port 80 and not on CI (docker)' do
+ let(:uri) { 'http://localhost' }
+
+ before do
+ allow(QA::Runtime::Env).to receive(:running_in_ci?).and_return(false)
+ end
+
+ it 'returns git user' do
+ expect(ssh.send(:git_user)).to eq('git')
+ end
+ end
+ end
+end
diff --git a/qa/spec/support/wait_for_requests_spec.rb b/qa/spec/support/wait_for_requests_spec.rb
index 79ee3eb5099..47c35addd9f 100644
--- a/qa/spec/support/wait_for_requests_spec.rb
+++ b/qa/spec/support/wait_for_requests_spec.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
-describe QA::Support::WaitForRequests do
+RSpec.describe QA::Support::WaitForRequests do
describe '.wait_for_requests' do
before do
- allow(subject).to receive(:finished_all_axios_requests?).and_return(true)
allow(subject).to receive(:finished_all_ajax_requests?).and_return(true)
allow(subject).to receive(:finished_loading?).and_return(true)
end
diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb
index 35f1e01289a..5b0c2c95d0d 100644
--- a/qa/spec/support/waiter_spec.rb
+++ b/qa/spec/support/waiter_spec.rb
@@ -2,7 +2,7 @@
require 'logger'
-describe QA::Support::Waiter do
+RSpec.describe QA::Support::Waiter do
before do
logger = ::Logger.new $stdout
logger.level = ::Logger::DEBUG