diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 07:33:21 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 07:33:21 +0000 |
commit | 36a59d088eca61b834191dacea009677a96c052f (patch) | |
tree | e4f33972dab5d8ef79e3944a9f403035fceea43f /qa | |
parent | a1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff) | |
download | gitlab-ce-36a59d088eca61b834191dacea009677a96c052f.tar.gz |
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'qa')
116 files changed, 1977 insertions, 1072 deletions
diff --git a/qa/.rspec b/qa/.rspec index b83d9b7aa65..68d481ca4d9 100644 --- a/qa/.rspec +++ b/qa/.rspec @@ -1,3 +1,5 @@ ---color +--force-color +--order random --format documentation +--default-path qa/specs --require spec_helper diff --git a/qa/.rspec_internal b/qa/.rspec_internal index ea32ca1e093..75786e123d4 100644 --- a/qa/.rspec_internal +++ b/qa/.rspec_internal @@ -1,4 +1,4 @@ --force-color --order random --format documentation ---require specs/spec_helper +--require spec_helper diff --git a/qa/Dockerfile b/qa/Dockerfile index fa666daa927..4fd44ba02df 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,33 +1,17 @@ -FROM ruby:2.7-buster +FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-git-2.33-lfs-2.9-chrome-99-docker-20.10.14-gcloud-383-kubectl-1.23 LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" -ENV DEBIAN_FRONTEND="noninteractive" -ENV DOCKER_VERSION="17.09.0-ce" +ENV DEBIAN_FRONTEND="noninteractive" \ + BUNDLE_WITHOUT=development ## -# Update APT sources and install dependencies +# Install system libs # -RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list -RUN apt-get update && apt-get install -y wget unzip xvfb lsb-release git git-lfs - -## -# Install Docker -# -RUN wget -q "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" && \ - tar -zxf "docker-${DOCKER_VERSION}.tgz" && mv docker/docker /usr/local/bin/docker && \ - rm "docker-${DOCKER_VERSION}.tgz" - -## -# Install client certificate - Bug in Chrome Headless: https://gitlab.com/gitlab-org/gitlab/-/issues/331492 -# -# RUN apt install -y libnss3-tools -# RUN mkdir -p $HOME/.pki/nssdb -# RUN certutil -N -d sql:$HOME/.pki/nssdb -# COPY ./qa/tls_certificates/client/client.pfx /tmp/client.pfx -# RUN pk12util -d sql:$HOME/.pki/nssdb -i /tmp/client.pfx -W '' -# RUN mkdir -p /etc/opt/chrome/policies/managed -# RUN echo '{ "AutoSelectCertificateForUrls": ["{\"pattern\":\"*\",\"filter\":{}}"] }' > /etc/opt/chrome/policies/managed/policy.json -# RUN cat /etc/opt/chrome/policies/managed/policy.json +RUN apt-get update; \ + apt-get install -y xvfb unzip; \ + apt-get -yq autoremove; \ + apt-get clean -yqq; \ + rm -rf /var/lib/apt/lists/* ## # Install root certificate @@ -37,39 +21,19 @@ ADD ./qa/tls_certificates/authority/ca.crt /usr/share/ca-certificates/gitlab/ RUN echo 'gitlab/ca.crt' >> /etc/ca-certificates.conf RUN chmod -R 644 /usr/share/ca-certificates/gitlab && update-ca-certificates -## -# Install gcloud and kubectl CLI used in Auto DevOps test to create K8s -# clusters -# -RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ - echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list && \ - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ - apt-get update -y && apt-get install google-cloud-sdk kubectl -y - -## -# Install Google Chrome version with headless support -# Download from our local S3 bucket, populated by https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/scripts/cache-google-chrome -# -# https://s3.amazonaws.com/gitlab-google-chrome-stable -ENV CHROME_VERSION="91.0.4472.77-1" -ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb" -ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}" -RUN curl --silent --show-error --fail -O "${CHROME_URL}" && \ - dpkg -i "./${CHROME_DEB}" || true && \ - apt-get install -f -y && \ - rm -f "./${CHROME_DEB}" - WORKDIR /home/gitlab/qa +## # Install qa dependencies or fetch from cache if unchanged +# COPY ./qa/Gemfile* /home/gitlab/qa/ -RUN gem install bundler --no-document --conservative --version 2.3.6 -RUN bundle install --jobs=$(nproc) --retry=3 --without=development --quiet +RUN bundle install --jobs=$(nproc) --retry=3 ## # Fetch chromedriver based on version of chrome # Copy rakefile first so that webdriver is not reinstalled on every code change # https://github.com/titusfortner/webdrivers +# COPY ./qa/tasks/webdrivers.rake /home/gitlab/qa/tasks/ RUN bundle exec rake -f tasks/webdrivers.rake webdrivers:chromedriver:update diff --git a/qa/README.md b/qa/README.md index 2e3f6af5bc3..724638d13c0 100644 --- a/qa/README.md +++ b/qa/README.md @@ -197,7 +197,7 @@ another test has `:ldap` and `:quarantine` metadata. If the tests are run with ### Running tests with a feature flag enabled or disabled -Tests can be run with with a feature flag enabled or disabled by using the command-line +Tests can be run with a feature flag enabled or disabled by using the command-line option `--enable-feature FEATURE_FLAG` or `--disable-feature FEATURE_FLAG`. For example, to enable the feature flag that enforces Gitaly request limits, diff --git a/qa/Rakefile b/qa/Rakefile index 0a65a58e6fc..e4d38d8294f 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -65,6 +65,11 @@ task :delete_test_users, [:delete_before, :dry_run, :exclude_users] do |t, args| QA::Tools::DeleteTestUsers.new(args).run end +desc "Deletes snippets" +task :delete_test_snippets, [:delete_before, :dry_run] do |t, args| + QA::Tools::DeleteTestSnippets.new(args).run +end + namespace :test_resources do desc "Deletes resources created during E2E test runs" task :delete, [:file_pattern] do |t, args| diff --git a/qa/lib/gitlab/page/group/settings/billing.rb b/qa/lib/gitlab/page/group/settings/billing.rb index 24d327502f8..d0d73278890 100644 --- a/qa/lib/gitlab/page/group/settings/billing.rb +++ b/qa/lib/gitlab/page/group/settings/billing.rb @@ -5,13 +5,30 @@ module Gitlab module Group module Settings class Billing < Chemlab::Page - # TODO: Supplant with data-qa-selectors - h4 :billing_plan_header, css: 'div.billing-plan-header h4' - + h4 :billing_plan_header link :start_your_free_trial + link :upgrade_to_premium + link :upgrade_to_ultimate + + # Subscription details + strong :subscription_header + button :refresh_seats + + # Usage + p :seats_in_subscription + p :seats_currently_in_use + link :see_seats_usage + p :max_seats_used + p :seats_owed + + # Billing + p :subscription_start_date + p :subscription_end_date - link :upgrade_to_premium, css: '[data-testid="plan-card-premium"] a.billing-cta-purchase-new' - link :upgrade_to_ultimate, css: '[data-testid="plan-card-ultimate"] a.billing-cta-purchase-new' + def refresh_subscription_seats + refresh_seats + ::QA::Support::WaitForRequests.wait_for_requests + end end end end diff --git a/qa/lib/gitlab/page/group/settings/billing.stub.rb b/qa/lib/gitlab/page/group/settings/billing.stub.rb index 64176af794a..c49d744d61f 100644 --- a/qa/lib/gitlab/page/group/settings/billing.stub.rb +++ b/qa/lib/gitlab/page/group/settings/billing.stub.rb @@ -100,6 +100,222 @@ module Gitlab def upgrade_to_ultimate? # This is a stub, used for indexing. The method is dynamically generated. end + + # @note Defined as +strong :subscription_header+ + # @return [String] The text content or value of +subscription_header+ + def subscription_header + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_header_element).to exist + # end + # @return [Watir::Strong] The raw +Strong+ element + def subscription_header_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_header + # end + # @return [Boolean] true if the +subscription_header+ element is present on the page + def subscription_header? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +button :refresh_seats+ + # Clicks +refresh_seats+ + def refresh_seats + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.refresh_seats_element).to exist + # end + # @return [Watir::Button] The raw +Button+ element + def refresh_seats_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_refresh_seats + # end + # @return [Boolean] true if the +refresh_seats+ element is present on the page + def refresh_seats? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_in_subscription+ + # @return [String] The text content or value of +seats_in_subscription+ + def seats_in_subscription + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_in_subscription_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_in_subscription_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_in_subscription + # end + # @return [Boolean] true if the +seats_in_subscription+ element is present on the page + def seats_in_subscription? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_currently_in_use+ + # @return [String] The text content or value of +seats_currently_in_use+ + def seats_currently_in_use + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_currently_in_use_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_currently_in_use_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_currently_in_use + # end + # @return [Boolean] true if the +seats_currently_in_use+ element is present on the page + def seats_currently_in_use? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +link :see_seats_usage+ + # Clicks +see_seats_usage+ + def see_seats_usage + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.see_seats_usage_element).to exist + # end + # @return [Watir::Link] The raw +Link+ element + def see_seats_usage_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_see_seats_usage + # end + # @return [Boolean] true if the +see_seats_usage+ element is present on the page + def see_seats_usage? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :max_seats_used+ + # @return [String] The text content or value of +max_seats_used+ + def max_seats_used + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.max_seats_used_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def max_seats_used_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_max_seats_used + # end + # @return [Boolean] true if the +max_seats_used+ element is present on the page + def max_seats_used? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :seats_owed+ + # @return [String] The text content or value of +seats_owed+ + def seats_owed + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.seats_owed_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def seats_owed_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_seats_owed + # end + # @return [Boolean] true if the +seats_owed+ element is present on the page + def seats_owed? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :subscription_start_date+ + # @return [String] The text content or value of +subscription_start_date+ + def subscription_start_date + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_start_date_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def subscription_start_date_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_start_date + # end + # @return [Boolean] true if the +subscription_start_date+ element is present on the page + def subscription_start_date? + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @note Defined as +p :subscription_end_date+ + # @return [String] The text content or value of +subscription_end_date+ + def subscription_end_date + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing.subscription_end_date_element).to exist + # end + # @return [Watir::P] The raw +P+ element + def subscription_end_date_element + # This is a stub, used for indexing. The method is dynamically generated. + end + + # @example + # Gitlab::Page::Group::Settings::Billing.perform do |billing| + # expect(billing).to be_subscription_end_date + # end + # @return [Boolean] true if the +subscription_end_date+ element is present on the page + def subscription_end_date? + # This is a stub, used for indexing. The method is dynamically generated. + end end end end diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb index 0408668a81c..df12fe4076c 100644 --- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb +++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb @@ -14,7 +14,7 @@ module Gitlab span :purchased_usage_total div :ci_purchase_successful_alert, text: /You have successfully purchased CI minutes/ div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ - h4 :storage_available_alert, text: /purchased storage is available/ + h2 :storage_available_alert, text: /purchased storage is available/ def plan_ci_limits plan_ci_minutes_element.span.text[%r{([^/ ]+)$}] @@ -24,6 +24,7 @@ module QA loader.push_dir(root, namespace: QA) loader.ignore("#{root}/specs/features") + loader.ignore("#{root}/specs/spec_helper.rb") loader.inflector.inflect( "ce" => "CE", @@ -63,5 +64,10 @@ module QA "registry_with_cdn" => "RegistryWithCDN" ) + # Configure knapsack at the very begining of the setup + loader.on_setup do + QA::Support::KnapsackReport.configure! + end + loader.setup end diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 71c538c20a0..bf08f887c7d 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -8,6 +8,7 @@ module QA def perform_before_hooks # The login page could take some time to load the first time it is visited. # We visit the login page and wait for it to properly load only once before the tests. + QA::Runtime::Logger.info("Performing sanity check for environment!") QA::Support::Retrier.retry_on_exception do QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) end diff --git a/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb index 303a64ad233..31543d30e88 100644 --- a/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb +++ b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb @@ -4,7 +4,6 @@ plugins { } repositories { - jcenter() maven { url "<%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven" name "GitLab" diff --git a/qa/qa/fixtures/web_ide/logo_sample.svg b/qa/qa/fixtures/web_ide/logo_sample.svg index 883e7e6cf92..211b511c8c7 100644 --- a/qa/qa/fixtures/web_ide/logo_sample.svg +++ b/qa/qa/fixtures/web_ide/logo_sample.svg @@ -1,27 +1,16 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"> +<svg width="50" height="48" viewBox="0 0 50 48" fill="none" xmlns="http://www.w3.org/2000/svg"> <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch --> <title>Slice 1</title> <desc>Created with Sketch.</desc> <script>alert('FAIL')</script> <defs></defs> - <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"> - <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)"> - <g id="Page-1" sketch:type="MSShapeGroup"> - <g id="Fill-1-+-Group-24"> - <g id="Group-24"> - <g id="Group"> - <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path> - <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path> - <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path> - <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path> - <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path> - <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path> - <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path> - </g> - </g> - </g> - </g> - </g> - </g> + <path d="m49.014 19-.067-.18-6.784-17.696a1.792 1.792 0 0 0-3.389.182l-4.579 14.02H15.651l-4.58-14.02a1.795 1.795 0 0 0-3.388-.182l-6.78 17.7-.071.175A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869 10.394-7.779.029-.022a12.595 12.595 0 0 0 4.182-14.554Z" + fill="#E24329"/> + <path d="m49.014 19-.067-.18a22.88 22.88 0 0 0-9.12 4.103L24.931 34.187l9.485 7.167 10.393-7.779.03-.022a12.595 12.595 0 0 0 4.175-14.554Z" + fill="#FC6D26"/> + <path d="m15.414 41.354 5.12 3.87 3.11 2.351a2.102 2.102 0 0 0 2.535 0l3.11-2.352 5.12-3.869-9.484-7.167-9.51 7.167Z" + fill="#FCA326"/> + <path d="M10.019 22.923a22.86 22.86 0 0 0-9.117-4.1L.832 19A12.595 12.595 0 0 0 5.01 33.556l.026.02.057.044 10.32 7.734 9.491-7.167L10.02 22.923Z" + fill="#FC6D26"/> </svg> diff --git a/qa/qa/flow/merge_request.rb b/qa/qa/flow/merge_request.rb index c26140000fe..f1cab2c7d1a 100644 --- a/qa/qa/flow/merge_request.rb +++ b/qa/qa/flow/merge_request.rb @@ -10,6 +10,26 @@ module QA Page::Project::Settings::Main.perform(&:expand_merge_requests_settings) Page::Project::Settings::MergeRequest.perform(&:enable_merge_train) end + + # Opens the form to create a new merge request. + # It tries to use the "Create merge request" button that appears after + # a commit is pushed, but if that button isn't available, it uses the + # "New merge request" button on the page that lists merge requests. + # + # @param [String] source_branch the branch to be merged + def create_new(source_branch:) + if Page::Project::Show.perform(&:has_create_merge_request_button?) + Page::Project::Show.perform(&:new_merge_request) + return + end + + Page::Project::Menu.perform(&:click_merge_requests) + Page::MergeRequest::Index.perform(&:click_new_merge_request) + Page::MergeRequest::New.perform do |merge_request| + merge_request.select_source_branch(source_branch) + merge_request.click_compare_branches_and_continue + end + end end end end diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 356e509a81d..2244a78b9e5 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -21,7 +21,7 @@ module QA # We set HOME to the current working directory (which is a # temporary directory created in .perform()) so the temporarily dropped # .netrc can be utilised - self.env_vars = [%Q{HOME="#{tmp_home_dir}"}] + self.env_vars = [%(HOME="#{tmp_home_dir}")] @use_lfs = false @gpg_key_id = nil @default_branch = Runtime::Env.default_branch @@ -71,7 +71,7 @@ module QA def checkout(branch_name, new_branch: false) opts = new_branch ? '-b' : '' - run_git(%Q{git checkout #{opts} "#{branch_name}"}).to_s + run_git(%(git checkout #{opts} "#{branch_name}")).to_s end def shallow_clone @@ -79,8 +79,8 @@ module QA end def configure_identity(name, email) - run_git(%Q{git config user.name "#{name}"}) - run_git(%Q{git config user.email #{email}}) + run_git(%(git config user.name "#{name}")) + run_git(%(git config user.email #{email})) end def commit_file(name, contents, message) @@ -94,11 +94,11 @@ module QA ::File.write(name, contents) if use_lfs? - git_lfs_track_result = run_git(%Q{git lfs track #{name} --lockable}) + git_lfs_track_result = run_git(%(git lfs track #{name} --lockable)) return git_lfs_track_result.response unless git_lfs_track_result.success? end - git_add_result = run_git(%Q{git add #{name}}) + git_add_result = run_git(%(git add #{name})) git_lfs_track_result.to_s + git_add_result.to_s end @@ -108,15 +108,15 @@ module QA end def delete_tag(tag_name) - run_git(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s + run_git(%(git push origin --delete #{tag_name}), max_attempts: 3).to_s end def commit(message) - run_git(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s + run_git(%(git commit -m "#{message}"), max_attempts: 3).to_s end def commit_with_gpg(message) - run_git(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s + run_git(%{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 @@ -159,11 +159,11 @@ module QA @ssh = Support::SSH.perform do |ssh| ssh.key = key ssh.uri = uri - ssh.setup(env: self.env_vars) + ssh.setup(env: env_vars) ssh end - self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{ssh.private_key_file.path} -o UserKnownHostsFile=#{ssh.known_hosts_file.path}"} + env_vars << %(GIT_SSH_COMMAND="ssh -i #{ssh.private_key_file.path} -o UserKnownHostsFile=#{ssh.known_hosts_file.path}") end def delete_ssh_key @@ -182,7 +182,9 @@ module QA end 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) + unless %w[0 1 2].include?(value.to_s) + raise ArgumentError, "Please specify the protocol you would like to use: 0, 1, or 2" + end run_git("git config protocol.version #{value}") end @@ -190,8 +192,8 @@ module QA 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("git ls-remote #{uri}", max_attempts: 3, env: [*self.env_vars, "GIT_TRACE_PACKET=1"]) - result.response[/git< version (\d+)/, 1] || 'unknown' + result = run_git("git ls-remote #{uri}", max_attempts: 3, env: [*env_vars, "GIT_TRACE_PACKET=1"]) + result.response[/ls-remote< version (\d+)/, 1] || 'unknown' end def try_add_credentials_to_netrc @@ -266,7 +268,7 @@ module QA # FileUtils.mkdir_p(tmp_home_dir) File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) } - File.chmod(0600, netrc_file_path) + File.chmod(0o600, netrc_file_path) end def tmp_home_dir @@ -302,7 +304,7 @@ module QA read_netrc_content.grep(/^#{Regexp.escape(netrc_content)}$/).any? end - def run_git(command_str, env: self.env_vars, max_attempts: 1) + def run_git(command_str, env: env_vars, max_attempts: 1) run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ') end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 83db8bc0fd6..248c5f38438 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -218,6 +218,15 @@ module QA page.validate_elements_present! if page end + # Uses capybara to locate and click an element instead of `click_element`. + # This can be used when it's not possible to add a QA selector but we still want to log the click + # + # @param [String] method the capybara method to use + # @param [String] locator the selector used to find the element + def click_via_capybara(method, locator) + page.public_send(method, locator) + end + def fill_element(name, content) find_element(name).set(content) end @@ -452,7 +461,7 @@ module QA click_by_js ? page.execute_script("arguments[0].click();", box) : box.click end - def feature_flag_controlled_element(feature_flag, element_when_flag_enabled, element_when_flag_disabled) + def feature_flag_controlled_element(feature_flag, element_when_flag_enabled, element_when_flag_disabled, visibility = true) # Feature flags can change the UI elements shown, but we need admin access to get feature flag values, which # prevents us running the tests on production. Instead we detect the UI element that should be shown when the # feature flag is enabled and otherwise use the element that should be displayed when the feature flag is @@ -463,12 +472,12 @@ module QA # load and render the UI wait_for_requests - return element_when_flag_enabled if has_element?(element_when_flag_enabled, wait: 1) - return element_when_flag_disabled if has_element?(element_when_flag_disabled, wait: 1) + return element_when_flag_enabled if has_element?(element_when_flag_enabled, wait: 1, visible: visibility) + return element_when_flag_disabled if has_element?(element_when_flag_disabled, wait: 1, visibile: visibility) # Check both options again, this time waiting for the default duration - return element_when_flag_enabled if has_element?(element_when_flag_enabled) - return element_when_flag_disabled if has_element?(element_when_flag_disabled) + return element_when_flag_enabled if has_element?(element_when_flag_enabled, visible: visibility) + return element_when_flag_disabled if has_element?(element_when_flag_disabled, visible: visibility) raise ElementNotFound, "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \ diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb index ce743b24dda..c2a1687ccfc 100644 --- a/qa/qa/page/component/blob_content.rb +++ b/qa/qa/page/component/blob_content.rb @@ -63,6 +63,17 @@ module QA end end + def has_normalized_ws_text?(text, wait: Capybara.default_max_wait_time) + if has_element?(:blob_viewer_file_content, wait: 1) + # The blob viewer renders line numbers and whitespace in a way that doesn't match the source file + # This isn't a visual validation test, so we ignore line numbers and whitespace + find_element(:blob_viewer_file_content, wait: 0).text.gsub(/^\d+\s|\s*/, '') + .start_with?(text.gsub(/\s*/, '')) + else + has_text?(text.gsub(/\s+/, " "), wait: wait) + end + end + def click_copy_file_contents(file_number = nil) within_file_by_number(:default_actions_container, file_number) { click_element(:copy_contents_button) } end diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb index 6ccf8a4043e..9c4408e36e4 100644 --- a/qa/qa/page/component/new_snippet.rb +++ b/qa/qa/page/component/new_snippet.rb @@ -76,8 +76,7 @@ module QA end def click_create_snippet_button - wait_until(reload: false) { !find_element(:submit_button).disabled? } - click_element(:submit_button) + click_element_coordinates(:submit_button) wait_until(reload: false) do has_no_element?(:snippet_title_field) end diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index a8ae706858e..47d32ae8225 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -82,7 +82,7 @@ module QA base.view 'app/views/layouts/nav/_breadcrumbs.html.haml' do element :breadcrumb_links_content - element :breadcrumb_sub_title_content + element :breadcrumb_current_link end end @@ -257,7 +257,7 @@ module QA def snippet_id within_element(:breadcrumb_links_content) do - find_element(:breadcrumb_sub_title_content).text.delete_prefix('$') + find_element(:breadcrumb_current_link).text.delete_prefix('$') end end end diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb index d84a053591c..8af3eb5693c 100644 --- a/qa/qa/page/dashboard/snippet/edit.rb +++ b/qa/qa/page/dashboard/snippet/edit.rb @@ -63,8 +63,7 @@ module QA end def save_changes - wait_until(reload: false) { !find_element(:submit_button).disabled? } - click_element(:submit_button) + click_element_coordinates(:submit_button) wait_until(reload: false) do has_no_element?(:file_name_field) end diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb index a6251f185f9..bb8934db498 100644 --- a/qa/qa/page/file/form.rb +++ b/qa/qa/page/file/form.rb @@ -4,8 +4,9 @@ module QA module Page module File class Form < Page::Base - include Shared::CommitMessage include Page::Component::DropdownFilter + include Page::Component::BlobContent + include Shared::CommitMessage include Shared::CommitButton include Shared::Editor diff --git a/qa/qa/page/group/settings/group_deploy_tokens.rb b/qa/qa/page/group/settings/group_deploy_tokens.rb index 65ee3fc72eb..7d908f473de 100644 --- a/qa/qa/page/group/settings/group_deploy_tokens.rb +++ b/qa/qa/page/group/settings/group_deploy_tokens.rb @@ -30,10 +30,10 @@ module QA end def fill_scopes(read_repository: false, read_registry: false, read_package_registry: false, write_package_registry: false ) - check_element(:deploy_token_read_repository_checkbox) if read_repository - check_element(:deploy_token_read_package_registry_checkbox) if read_package_registry - check_element(:deploy_token_read_registry_checkbox) if read_registry - check_element(:deploy_token_write_package_registry_checkbox) if write_package_registry + check_element(:deploy_token_read_repository_checkbox, true) if read_repository + check_element(:deploy_token_read_package_registry_checkbox, true) if read_package_registry + check_element(:deploy_token_read_registry_checkbox, true) if read_registry + check_element(:deploy_token_write_package_registry_checkbox, true) if write_package_registry end def add_token diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index d8b7bb90437..f3ee627c41e 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -30,9 +30,8 @@ module QA element :register_tab end - view 'app/views/devise/shared/_tabs_normal.html.haml' do + view 'app/views/devise/shared/_tab_single.html.haml' do element :sign_in_tab - element :register_tab end view 'app/helpers/auth_helper.rb' do @@ -134,6 +133,14 @@ module QA has_css?('[name="arkose_labs_token"][value]', visible: false) end + def has_accept_all_cookies_button? + has_button?('Accept All Cookies') + end + + def click_accept_all_cookies + click_button('Accept All Cookies') + end + def switch_to_sign_in_tab click_element :sign_in_tab end @@ -180,6 +187,7 @@ module QA fill_element :password_field, user.password if Runtime::Env.running_on_dot_com? + click_accept_all_cookies if has_accept_all_cookies_button? # Arkose only appears in staging.gitlab.com, gitlab.com, etc... # Wait until the ArkoseLabs challenge has initialized diff --git a/qa/qa/page/merge_request/index.rb b/qa/qa/page/merge_request/index.rb new file mode 100644 index 00000000000..ae024c16566 --- /dev/null +++ b/qa/qa/page/merge_request/index.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Page + module MergeRequest + class Index < Page::Base + view 'app/views/shared/empty_states/_merge_requests.html.haml' do + element :new_merge_request_button + end + + def click_new_merge_request + click_element(:new_merge_request_button) + end + end + end + end +end diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index bcc60a8275d..b19a0d1a830 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -12,6 +12,11 @@ module QA element :issuable_form_description end + view 'app/views/projects/merge_requests/creations/_new_compare.html.haml' do + element :compare_branches_button + element :source_branch_dropdown + end + view 'app/views/projects/merge_requests/show.html.haml' do element :diffs_tab end @@ -27,6 +32,10 @@ module QA "to customize #{scanner_name} settings." end + def click_compare_branches_and_continue + click_element(:compare_branches_button) + end + def create_merge_request click_element(:issuable_create_button, Page::MergeRequest::Show) end @@ -43,6 +52,12 @@ module QA def has_file?(file_name) has_element?(:file_name_content, text: file_name) end + + def select_source_branch(branch) + click_element(:source_branch_dropdown) + fill_element(:dropdown_input_field, branch) + click_via_capybara(:click_on, branch) + end end end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 689b3dba286..8f5ac62d127 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -49,8 +49,8 @@ module QA element :comment_now_button end - view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do - element :download_dropdown + view 'app/views/projects/merge_requests/_code_dropdown.html.haml' do + element :mr_code_dropdown element :download_email_patches_menu_item element :download_plain_diff_menu_item element :open_in_web_ide_button @@ -343,12 +343,12 @@ module QA end def view_email_patches - click_element(:download_dropdown) + click_element(:mr_code_dropdown) visit_link_in_element(:download_email_patches_menu_item) end def view_plain_diff - click_element(:download_dropdown) + click_element(:mr_code_dropdown) visit_link_in_element(:download_plain_diff_menu_item) end @@ -359,6 +359,7 @@ module QA end def click_open_in_web_ide + click_element(:mr_code_dropdown) click_element(:open_in_web_ide_button) wait_for_requests end diff --git a/qa/qa/page/project/pipeline/new.rb b/qa/qa/page/project/pipeline/new.rb index 96a48e6240a..6cf5c3b1134 100644 --- a/qa/qa/page/project/pipeline/new.rb +++ b/qa/qa/page/project/pipeline/new.rb @@ -16,9 +16,9 @@ module QA click_element(:run_pipeline_button, Page::Project::Pipeline::Show) end - def add_variable(key, value, row_index: 0) + def configure_variable(key: nil, value: 'foo', row_index: 0) within_element_by_index(:ci_variable_row_container, row_index) do - fill_element(:ci_variable_key_field, key) + fill_element(:ci_variable_key_field, key) unless key.nil? fill_element(:ci_variable_value_field, value) end end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index f499b748fb4..3f1cc2f1257 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -74,7 +74,11 @@ module QA end def has_linked_pipeline?(title: nil) - title ? find_linked_pipeline_by_title(title) : has_element?(:linked_pipeline_container) + # If the pipeline page has loaded linked pipelines should appear, but it can take a little while, + # especially on busier environments. + retry_until(reload: true, message: 'Waiting for linked pipeline to appear') do + title ? find_linked_pipeline_by_title(title) : has_element?(:linked_pipeline_container) + end end alias_method :has_child_pipeline?, :has_linked_pipeline? diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb index 1a8e1e07994..197fd8fd9fb 100644 --- a/qa/qa/page/project/pipeline_editor/show.rb +++ b/qa/qa/page/project/pipeline_editor/show.rb @@ -5,6 +5,10 @@ module QA module Project module PipelineEditor class Show < QA::Page::Base + view 'app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue' do + element :pipeline_editor_app, required: true + end + view 'app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue' do element :branch_selector_button, required: true element :branch_menu_item_button @@ -12,7 +16,7 @@ module QA end view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do - element :target_branch_field, required: true + element :source_branch_field, required: true end view 'app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue' do @@ -46,7 +50,23 @@ module QA element :file_editor_container end + view 'app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue' do + element :file_tree_popover + end + + def initialize + dismiss_file_tree_popover if has_element?(:file_tree_popover) + + super + end + + def dismiss_file_tree_popover + # clicking outside the popover will dismiss it + click_element(:pipeline_editor_app) + end + def open_branch_selector_dropdown + dismiss_file_tree_popover if has_element?(:file_tree_popover, wait: 1) click_element(:branch_selector_button) end @@ -57,8 +77,8 @@ module QA wait_for_requests end - def target_branch_name - find_element(:target_branch_field).value + def source_branch_name + find_element(:source_branch_field).value end def editing_content @@ -76,8 +96,8 @@ module QA wait_for_requests end - def set_target_branch(name) - find_element(:target_branch_field).fill_in(with: name) + def set_source_branch(name) + find_element(:source_branch_field).fill_in(with: name) end def current_branch diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 6df285cdd93..c5fad2efcfe 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -11,13 +11,6 @@ module QA element :autodevops_settings_content element :runners_settings_content element :variables_settings_content - element :general_pipelines_settings_content - end - - def expand_general_pipelines(&block) - expand_content(:general_pipelines_settings_content) do - Settings::GeneralPipelines.perform(&block) - end end def expand_runners_settings(&block) diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb index 407d57bc54e..cf25f4a0568 100644 --- a/qa/qa/page/project/settings/deploy_tokens.rb +++ b/qa/qa/page/project/settings/deploy_tokens.rb @@ -31,11 +31,25 @@ module QA end def fill_scopes(scopes) - check_element(:deploy_token_read_repository_checkbox) if scopes.include? :read_repository - check_element(:deploy_token_read_package_registry_checkbox) if scopes.include? :read_package_registry - check_element(:deploy_token_write_package_registry_checkbox) if scopes.include? :write_package_registry - check_element(:deploy_token_read_registry_checkbox) if scopes.include? :read_registry - check_element(:deploy_token_write_registry_checkbox) if scopes.include? :write_registry + if scopes.include? :read_repository + check_element(:deploy_token_read_repository_checkbox, true) + end + + if scopes.include? :read_package_registry + check_element(:deploy_token_read_package_registry_checkbox, true) + end + + if scopes.include? :write_package_registry + check_element(:deploy_token_write_package_registry_checkbox, true) + end + + if scopes.include? :read_registry + check_element(:deploy_token_read_registry_checkbox, true) + end + + if scopes.include? :write_registry + check_element(:deploy_token_write_registry_checkbox, true) + end end def add_token diff --git a/qa/qa/page/project/settings/general_pipelines.rb b/qa/qa/page/project/settings/general_pipelines.rb deleted file mode 100644 index 5a98849a41d..00000000000 --- a/qa/qa/page/project/settings/general_pipelines.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Settings - class GeneralPipelines < Page::Base - include QA::Page::Settings::Common - - view 'app/views/projects/settings/ci_cd/_form.html.haml' do - element :build_coverage_regex_field - element :save_general_pipelines_changes_button - end - - def configure_coverage_regex(pattern) - fill_element :build_coverage_regex_field, pattern - click_element :save_general_pipelines_changes_button - end - end - end - end - end -end diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index dbe804bfdd0..0d5d4df9f34 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -29,7 +29,7 @@ module QA end def enable_merge_if_all_disscussions_are_resolved - check_element(:allow_merge_if_all_discussions_are_resolved_checkbox) + check_element(:allow_merge_if_all_discussions_are_resolved_checkbox, true) click_save_changes end end diff --git a/qa/qa/page/project/settings/services/prometheus.rb b/qa/qa/page/project/settings/services/prometheus.rb index 8ae4ded535e..2e3c385a27d 100644 --- a/qa/qa/page/project/settings/services/prometheus.rb +++ b/qa/qa/page/project/settings/services/prometheus.rb @@ -8,7 +8,7 @@ module QA class Prometheus < Page::Base include Page::Component::CustomMetric - view 'app/views/projects/services/prometheus/_custom_metrics.html.haml' do + view 'app/views/shared/integrations/prometheus/_custom_metrics.html.haml' do element :custom_metrics_container element :new_metric_button end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index b234a9ba986..9983ee2a33d 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -120,6 +120,10 @@ module QA click_element(:new_issue_link) end + def has_create_merge_request_button? + has_css?(element_selector_css(:create_merge_request)) + end + def has_file?(name) return false unless has_element?(:file_tree_table) @@ -144,7 +148,7 @@ module QA def new_merge_request wait_until(reload: true) do - has_css?(element_selector_css(:create_merge_request)) + has_create_merge_request_button? end click_element :create_merge_request diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index 889197a0373..bda72703906 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -64,10 +64,6 @@ module QA end end - def marked_for_deletion? - reload!.api_response[:marked_for_deletion_on].present? - end - # Get group badges # # @return [Array<QA::Resource::GroupBadge>] diff --git a/qa/qa/resource/group_deploy_token.rb b/qa/qa/resource/group_deploy_token.rb index c1d6be6547a..4c9b296ece1 100644 --- a/qa/qa/resource/group_deploy_token.rb +++ b/qa/qa/resource/group_deploy_token.rb @@ -17,10 +17,6 @@ module QA end end - def fabricate_via_api! - super - end - def api_get_path "/groups/#{group.id}/deploy_tokens" end diff --git a/qa/qa/resource/job.rb b/qa/qa/resource/job.rb new file mode 100644 index 00000000000..96c502e567c --- /dev/null +++ b/qa/qa/resource/job.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + module Resource + class Job < Base + attr_accessor :id, :name, :project + + attributes :id, + :project + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def artifacts + parse_body(api_get_from(api_get_path))[:artifacts] + end + + def api_get_path + "/projects/#{project.id}/jobs/#{id}" + end + + def api_post_path + end + + def api_post_body + { + artifacts: artifacts + } + end + end + end +end diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index 83adb10c3a0..0061f74cec5 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -12,7 +12,8 @@ module QA QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}]) response = post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level } - response.code == QA::Support::API::HTTP_STATUS_CREATED + break true if response.code == QA::Support::API::HTTP_STATUS_CREATED + break true if response.body.include?('Member already exists') end end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 685cccea02d..0a92553690f 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -78,12 +78,12 @@ module QA end def fabricate! - return fabricate_large_merge_request if Runtime::Scenario.large_setup? + return fabricate_large_merge_request if large_setup? populate_target_and_source_if_required project.visit! - Page::Project::Show.perform(&:new_merge_request) + Flow::MergeRequest.create_new(source_branch: source_branch) Page::MergeRequest::New.perform do |new_page| new_page.fill_title(@title) new_page.choose_template(@template) if @template @@ -100,7 +100,7 @@ module QA end def fabricate_via_api! - return fabricate_large_merge_request if Runtime::Scenario.large_setup? + return fabricate_large_merge_request if large_setup? resource_web_url(api_get) rescue ResourceNotFoundError, NoValueError # rescue if iid not populated @@ -208,6 +208,12 @@ module QA private + def large_setup? + Runtime::Scenario.large_setup? + rescue ArgumentError + false + end + def transform_api_resource(api_resource) raise ResourceNotFoundError if api_resource.blank? diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb index 907a6cb8558..910065d76a8 100644 --- a/qa/qa/resource/pipeline.rb +++ b/qa/qa/resource/pipeline.rb @@ -9,10 +9,10 @@ module QA end end - attribute :id - attribute :status - attribute :ref - attribute :sha + attributes :id, + :status, + :ref, + :sha # array in form # [ @@ -33,6 +33,14 @@ module QA Page::Project::Pipeline::New.perform(&:click_run_pipeline_button) end + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + rescue NoValueError + super + end + def ref project.default_branch end @@ -51,6 +59,40 @@ module QA variables: variables } end + + def pipeline_variables + response = get(request_url("#{api_get_path}/variables")) + + unless response.code == HTTP_STATUS_OK + raise ResourceQueryError, "Could not get variables. Request returned (#{response.code}): `#{response}`." + end + + parse_body(response) + end + + def has_variable?(key:, value:) + pipeline_variables.any? { |var| var[:key] == key && var[:value] == value } + end + + def has_no_variable?(key:, value:) + !pipeline_variables.any? { |var| var[:key] == key && var[:value] == value } + end + + def pipeline_bridges + response = get(request_url("#{api_get_path}/bridges")) + + unless response.code == HTTP_STATUS_OK + raise ResourceQueryError, "Could not get bridges. Request returned (#{response.code}): `#{response}`." + end + + parse_body(response) + end + + def downstream_pipeline_id(bridge_name:) + result = pipeline_bridges.find { |bridge| bridge[:name] == bridge_name } + + result[:downstream_pipeline][:id] + end end end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index dba3eb2e219..2db4f4b5f65 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -190,6 +190,10 @@ module QA "#{api_get_path}/pipeline_schedules" end + def api_jobs_path + "#{api_get_path}/jobs" + end + def api_issues_path "#{api_get_path}/issues" end @@ -206,6 +210,10 @@ module QA "#{api_get_path}/wikis" end + def api_releases_path + "#{api_get_path}/releases" + end + def api_post_body post_body = { name: name, @@ -354,6 +362,15 @@ module QA auto_paginated_response(request_url(api_pipelines_path, per_page: '100'), attempts: attempts) end + def jobs + response = get(request_url(api_jobs_path)) + parse_body(response) + end + + def job_by_name(job_name) + jobs.find { |job| job[:name] == job_name } + end + def issues(auto_paginate: false, attempts: 0) return parse_body(api_get_from(api_issues_path)) unless auto_paginate @@ -381,9 +398,22 @@ module QA api_post_to(api_wikis_path, title: title, content: content) end + def releases + response = api_get_from(api_releases_path) + parse_body(response) + end + + def create_release(tag, ref = default_branch, **params) + api_post_to(api_releases_path, tag_name: tag, ref: ref, **params) + end + # Uses the API to wait until a pull mirroring update is successful (pull mirroring is treated as an import) def wait_for_pull_mirroring - mirror_succeeded = Support::Retrier.retry_until(max_duration: 180, raise_on_failure: false, sleep_interval: 1) do + mirror_succeeded = Support::Retrier.retry_until( + max_duration: 180, + raise_on_failure: false, + sleep_interval: 1 + ) do reload! api_resource[:import_status] == "finished" end @@ -394,7 +424,11 @@ module QA def remove_via_api! super - Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1, message: "Waiting for #{self.class.name} to be removed") do + Support::Retrier.retry_until( + max_duration: 60, + sleep_interval: 1, + message: "Waiting for #{self.class.name} to be removed" + ) do !exists? rescue InternalServerError # Retry on transient errors that are likely to be due to race conditions between concurrent delete operations diff --git a/qa/qa/resource/project_access_token.rb b/qa/qa/resource/project_access_token.rb index f5cd8798f19..58cb3e667c0 100644 --- a/qa/qa/resource/project_access_token.rb +++ b/qa/qa/resource/project_access_token.rb @@ -15,10 +15,6 @@ module QA Page::Project::Settings::AccessTokens.perform(&:created_access_token) end - def fabricate_via_api! - super - end - def api_get_path "/projects/#{project.api_resource[:id]}/access_tokens" end diff --git a/qa/qa/resource/project_deploy_token.rb b/qa/qa/resource/project_deploy_token.rb index b31a7c25157..d6125103025 100644 --- a/qa/qa/resource/project_deploy_token.rb +++ b/qa/qa/resource/project_deploy_token.rb @@ -17,10 +17,6 @@ module QA end end - def fabricate_via_api! - super - end - def api_get_path "/projects/#{project.id}/deploy_tokens" end diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 28a0f12b3e3..b9dbd2a6131 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -17,7 +17,7 @@ module QA Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(github_personal_access_token) import_page.import!(github_repository_path, group.full_path, name) - import_page.wait_for_success(github_repository_path) + import_page.wait_for_success(github_repository_path, wait: 240) end reload! diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb index 54093a5c195..1fe35f7a77d 100644 --- a/qa/qa/resource/repository/commit.rb +++ b/qa/qa/resource/repository/commit.rb @@ -33,7 +33,11 @@ module QA super rescue ResourceNotFoundError - super + result = super + + project.wait_for_push(commit_message) + + result end def api_get_path diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 89e84f414b1..03178661826 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -18,10 +18,6 @@ module QA CAPYBARA_MAX_WAIT_TIME = 10 - def initialize - self.class.configure! - end - def self.blank_page? ['', 'about:blank', 'data:,'].include?(Capybara.current_session.driver.browser.current_url) rescue StandardError @@ -49,6 +45,8 @@ module QA # rubocop: disable Metrics/AbcSize def self.configure! + return if @configured + RSpec.configure do |config| config.define_derived_metadata(file_path: %r{/qa/specs/features/}) do |metadata| metadata[:type] = :feature @@ -57,23 +55,19 @@ module QA config.append_after(:each) do |example| if example.metadata[:screenshot] screenshot = example.metadata[:screenshot][:image] || example.metadata[:screenshot][:html] - example.metadata[:stdout] = %{[[ATTACHMENT|#{screenshot}]]} + example.metadata[:stdout] = %([[ATTACHMENT|#{screenshot}]]) end end end Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i - return if Capybara.drivers.include?(:chrome) - Capybara.register_driver QA::Runtime::Env.browser do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.send(QA::Runtime::Env.browser) case QA::Runtime::Env.browser when :chrome - if QA::Runtime::Env.accept_insecure_certs? - capabilities['acceptInsecureCerts'] = true - end + capabilities['acceptInsecureCerts'] = true if QA::Runtime::Env.accept_insecure_certs? # set logging preferences # this enables access to logs with `page.driver.manage.get_log(:browser)` @@ -103,7 +97,9 @@ module QA # Specify the user-agent to allow challenges to be bypassed # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938 - capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}" if QA::Runtime::Env.user_agent + if QA::Runtime::Env.user_agent + capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}" + end if QA::Runtime::Env.remote_mobile_device_name capabilities['platformName'] = 'Android' @@ -121,9 +117,7 @@ module QA end when :firefox - if QA::Runtime::Env.accept_insecure_certs? - capabilities['acceptInsecureCerts'] = true - end + capabilities['acceptInsecureCerts'] = true if QA::Runtime::Env.accept_insecure_certs? end # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true. @@ -162,7 +156,10 @@ module QA Capybara::Screenshot.append_timestamp = false Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example| - ::File.join(QA::Runtime::Namespace.name(reset_cache: false), example.full_description.downcase.parameterize(separator: "_")[0..99]) + ::File.join( + QA::Runtime::Namespace.name(reset_cache: false), + example.full_description.downcase.parameterize(separator: "_")[0..99] + ) end Capybara.configure do |config| @@ -183,6 +180,8 @@ module QA config.base_url = Runtime::Scenario.attributes[:gitlab_address] # reuse GitLab address config.hide_banner = true end + + @configured = true end # rubocop: enable Metrics/AbcSize @@ -221,7 +220,7 @@ module QA end end - yield.tap { clear! } if block_given? + yield.tap { clear! } if block end # To redirect the browser to a canary or non-canary web node diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index e4537009406..0db5314de4d 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -25,6 +25,10 @@ module QA SUPPORTED_FEATURES end + def gitlab_url + @gitlab_url ||= ENV["QA_GITLAB_URL"] || "http://127.0.0.1:3000" # default to GDK + end + def additional_repository_storage ENV['QA_ADDITIONAL_REPOSITORY_STORAGE'] end @@ -38,7 +42,7 @@ module QA end def interception_enabled? - enabled?(ENV['INTERCEPT_REQUESTS'], default: true) + enabled?(ENV['QA_INTERCEPT_REQUESTS'], default: false) end def can_intercept? diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index ec28813c1f6..93e215e9635 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -42,6 +42,8 @@ module QA enable(flag, **scopes) when 'disabled', 'disable', 'false', 0, false disable(flag, **scopes) + when 'deleted' + QA::Runtime::Logger.info("Feature flag definition for '#{flag}' was deleted. The state of the feature flag has not been changed.") else raise UnknownStateError, "Unknown feature flag state: #{state}" end diff --git a/qa/qa/runtime/ip_address.rb b/qa/qa/runtime/ip_address.rb index bec5c412a6a..657dc789cff 100644 --- a/qa/qa/runtime/ip_address.rb +++ b/qa/qa/runtime/ip_address.rb @@ -13,7 +13,7 @@ module QA def fetch_current_ip_address # When running on CI against a live environment such as staging.gitlab.com, # we use the public facing IP address - ip_address = if Env.running_in_ci? && !URI.parse(Scenario.gitlab_address).host.include?('test') + ip_address = if Env.running_in_ci? && !URI.parse(Scenario.gitlab_address).host.include?('.test') response = get(PUBLIC_IP_ADDRESS_API) raise HostUnreachableError, "#{PUBLIC_IP_ADDRESS_API} is unreachable" unless response.code == Support::API::HTTP_STATUS_OK diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb index 8cf1fa0705f..99906ca44d9 100644 --- a/qa/qa/scenario/template.rb +++ b/qa/qa/scenario/template.rb @@ -21,33 +21,30 @@ module QA end def perform(options, *args) - extract_address(:gitlab_address, options, args) - - gitlab_address = URI(Runtime::Scenario.gitlab_address) - - # Define the "About" page as an `about` subdomain. - # @example - # Given *gitlab_address* = 'https://gitlab.com/' #=> https://about.gitlab.com/ - # Given *gitlab_address* = 'https://staging.gitlab.com/' #=> https://about.staging.gitlab.com/ - # Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/ - Runtime::Scenario.define(:about_address, URI(-> { gitlab_address.host = "about.#{gitlab_address.host}"; gitlab_address }.call).to_s) # rubocop:disable Style/Semicolon + define_gitlab_address(options, args) # Save the scenario class name Runtime::Scenario.define(:klass, self.class.name) + # Set large setup attribute + Runtime::Scenario.define(:large_setup?, args.include?('can_use_large_setup')) + ## - # Setup knapsack and download latest report + # Configure browser # - Tools::KnapsackReport.configure! if Runtime::Env.knapsack? + Runtime::Browser.configure! ## # Perform before hooks, which are different for CE and EE # - - Runtime::Release.perform_before_hooks unless Runtime::Env.dry_run + QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature) - Runtime::Feature.disable(options[:disable_feature]) if options.key?(:disable_feature) && (@feature_enabled = Runtime::Feature.enabled?(options[:disable_feature])) + + if options.key?(:disable_feature) && (@feature_enabled = Runtime::Feature.enabled?(options[:disable_feature])) + Runtime::Feature.disable(options[:disable_feature]) + end + Runtime::Feature.set(options[:set_feature_flags]) if options.key?(:set_feature_flags) Specs::Runner.perform do |specs| @@ -60,25 +57,31 @@ module QA Runtime::Feature.enable(options[:disable_feature]) if options.key?(:disable_feature) && @feature_enabled end - def extract_option(name, options, args) - option = if options.key?(name) - options[name] - else - args.shift - end - - Runtime::Scenario.define(name, option) + def extract_address(name, options) + address = options[name] + validate_address(name, address) - option + Runtime::Scenario.define(name, address) end - # For backwards-compatibility, if the gitlab instance address is not - # specified as an option parsed by OptionParser, it can be specified as - # the first argument - def extract_address(name, options, args) - address = extract_option(name, options, args) + private + + delegate :define_gitlab_address_attribute!, to: 'QA::Support::GitlabAddress' + + # Define gitlab address attribute + # + # Use first argument if a valid address, else use named argument or default to environment variable + # + # @param [Hash] options + # @param [Array] args + # @return [void] + def define_gitlab_address(options, args) + address_from_opt = Runtime::Scenario.attributes[:gitlab_address] + + return define_gitlab_address_attribute!(args.shift) if args.first && Runtime::Address.valid?(args.first) + return define_gitlab_address_attribute!(address_from_opt) if address_from_opt - raise ::ArgumentError, "The address provided for `#{name}` is not valid: #{address}" unless Runtime::Address.valid?(address) + define_gitlab_address_attribute! end end end diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb index 48ebb51966c..145c767a38c 100644 --- a/qa/qa/scenario/test/integration/mattermost.rb +++ b/qa/qa/scenario/test/integration/mattermost.rb @@ -12,13 +12,6 @@ module QA tags :mattermost attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server' - - def perform(options, *args) - extract_address(:gitlab_address, options, args) - extract_address(:mattermost_address, options, args) - - super(options, *args) - end end end end diff --git a/qa/qa/service/cluster_provider/k3s_cilium.rb b/qa/qa/service/cluster_provider/k3s_cilium.rb deleted file mode 100644 index 5b529caa20b..00000000000 --- a/qa/qa/service/cluster_provider/k3s_cilium.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -module QA - module Service - module ClusterProvider - class K3sCilium < K3s - def setup - @k3s = Service::DockerRun::K3s.new.tap do |k3s| - k3s.remove! - k3s.cni_enabled = true - k3s.register! - - shell "kubectl config set-cluster k3s --server https://#{k3s.host_name}:6443 --insecure-skip-tls-verify" - shell 'kubectl config set-credentials default --username=node --password=some-secret' - shell 'kubectl config set-context k3s --cluster=k3s --user=default' - shell 'kubectl config use-context k3s' - - wait_for_server(k3s.host_name) do - shell 'kubectl version' - # install local storage - shell 'kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml' - - # patch local storage - shell %(kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}') - shell 'kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.8/install/kubernetes/quick-install.yaml' - - wait_for_namespaces do - wait_for_cilium - wait_for_coredns do - shell 'kubectl create -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.31.0/deploy/static/provider/cloud/deploy.yaml' - wait_for_ingress - end - end - end - end - end - - private - - def wait_for_cilium - QA::Runtime::Logger.info 'Waiting for Cilium pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces -l k8s-app=cilium --no-headers=true | grep -o "cilium-.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'Cilium pod has not initialized correctly' - end - - def wait_for_coredns - QA::Runtime::Logger.info 'Waiting for CoreDNS pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces --no-headers=true | grep -o "coredns.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'CoreDNS pod has not been initialized correctly' - end - - def wait_for_ingress - QA::Runtime::Logger.info 'Waiting for Ingress controller pod to be initialized' - - 60.times do - if service_available?('kubectl get pods --all-namespaces -l app.kubernetes.io/component=controller | grep -o "ingress-nginx-controller.*1/1"') - return yield if block_given? - - return true - end - - sleep 1 - QA::Runtime::Logger.info '.' - end - - raise 'Ingress pod has not been initialized correctly' - end - end - end - end -end diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb index 79bba484bea..79509bdbe01 100644 --- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb @@ -36,7 +36,7 @@ module QA imported_project.reload! # import the project expect { imported_project.project_import_status[:import_status] }.to eventually_eq('finished') - .within(max_duration: 90, sleep_interval: 1) + .within(max_duration: 240, sleep_interval: 1) aggregate_failures do verify_status_data diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb new file mode 100644 index 00000000000..201b8efdf6a --- /dev/null +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require_relative 'gitlab_project_migration_common' + +module QA + RSpec.describe 'Manage' do + describe 'Gitlab migration' do + include_context 'with gitlab project migration' + + context 'with release' do + let(:tag) { 'v0.0.1' } + let(:source_project_with_readme) { true } + + let(:milestone) do + Resource::ProjectMilestone.fabricate_via_api! do |resource| + resource.project = source_project + resource.api_client = api_client + end + end + + let(:source_release) { comparable_release(source_project.releases.find { |r| r[:tag_name] == tag }) } + let(:imported_release) { comparable_release(imported_releases.find { |r| r[:tag_name] == tag }) } + let(:imported_releases) { imported_project.releases } + + # Update release object to be comparable + # + # Convert objects with project specific attributes like paths and urls to be comparable + # + # @param [Hash] release + # @return [Hash] + def comparable_release(release) + release&.except(:_links, :evidences)&.merge( + { + author: release[:author].except(:web_url), + commit: release[:commit].except(:web_url), + commit_path: release[:commit_path].split("/-/").last, + tag_path: release[:tag_path].split("/-/").last, + assets: release[:assets].merge({ + sources: release.dig(:assets, :sources).map do |source| + source.merge({ url: source[:url].split("/-/").last }) + end + }), + milestones: release[:milestones].map do |milestone| + milestone.except(:id, :project_id).merge({ web_url: milestone[:web_url].split("/-/").last }) + end + # TODO: Add back evidence testing once implemented + # https://gitlab.com/gitlab-org/gitlab/-/issues/360567 + # evidences: release[:evidences].map do |evidence| + # evidence.merge({ filepath: evidence[:filepath].split("/-/").last }) + # end + } + ) + end + + before do + source_project.create_release(tag, milestones: [milestone.title]) + end + + it( + 'successfully imports project release', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360243' + ) do + expect_import_finished + + expect(imported_releases.size).to eq(1), "Expected to have 1 migrated release" + expect(imported_release).to eq(source_release) + end + end + end + end +end diff --git a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb index 6480b880400..33ec24d1be1 100644 --- a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb +++ b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb @@ -11,7 +11,7 @@ module QA @user_api_client = Runtime::API::Client.new(:gitlab, personal_access_token: @project_access_token.token) end - context 'for the same project' do + context 'for the same project', :reliable do it 'can be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347858' do expect do Resource::File.fabricate_via_api! do |file| diff --git a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb index fe6c89f4ee4..e518bbfc6f7 100644 --- a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb +++ b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Manage' do - describe 'User', :requires_admin do + describe 'User', :requires_admin, :reliable do before(:all) do admin_api_client = Runtime::API::Client.as_admin diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb index d066953d12e..b6296b5a263 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb @@ -3,7 +3,10 @@ require 'parallel' module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361382', + type: :investigating + } do context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env do let(:praefect_manager) { Service::PraefectManager.new } let(:project) do diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb index 4d28937fbf8..151fd0fffe3 100644 --- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb @@ -3,68 +3,69 @@ require 'airborne' module QA - RSpec.describe 'API basics' do - before(:context) do - @api_client = Runtime::API::Client.new(:gitlab) - end + RSpec.describe 'Create' do + describe 'API basics' do + before(:context) do + @api_client = Runtime::API::Client.new(:gitlab) + end - let(:project_name) { "api-basics-#{SecureRandom.hex(8)}" } - let(:sanitized_project_path) { CGI.escape("#{Runtime::User.username}/#{project_name}") } + let(:project_name) { "api-basics-#{SecureRandom.hex(8)}" } + let(:sanitized_project_path) { CGI.escape("#{Runtime::User.username}/#{project_name}") } - it 'user creates a project with a file and deletes them afterwards', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347745' do - create_project_request = Runtime::API::Request.new(@api_client, '/projects') - post create_project_request.url, path: project_name, name: project_name + it 'user creates a project with a file and deletes them afterwards', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347745' do + create_project_request = Runtime::API::Request.new(@api_client, '/projects') + post create_project_request.url, path: project_name, name: project_name - expect_status(201) - expect(json_body).to match( - a_hash_including(name: project_name, path: project_name) - ) + expect_status(201) + expect(json_body).to match( + a_hash_including(name: project_name, path: project_name) + ) - default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] + default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] - create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md") - post create_file_request.url, branch: default_branch, content: 'Hello world', commit_message: 'Add README.md' + create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md") + post create_file_request.url, branch: default_branch, content: 'Hello world', commit_message: 'Add README.md' - expect_status(201) - expect(json_body).to match( - a_hash_including(branch: default_branch, file_path: 'README.md') - ) + expect_status(201) + expect(json_body).to match( + a_hash_including(branch: default_branch, file_path: 'README.md') + ) - get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", ref: default_branch) - get get_file_request.url + get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", ref: default_branch) + get get_file_request.url - expect_status(200) - expect(json_body).to match( - a_hash_including( - ref: default_branch, - file_path: 'README.md', file_name: 'README.md', - encoding: 'base64', content: 'SGVsbG8gd29ybGQ=' + expect_status(200) + expect(json_body).to match( + a_hash_including( + ref: default_branch, + file_path: 'README.md', file_name: 'README.md', + encoding: 'base64', content: 'SGVsbG8gd29ybGQ=' + ) ) - ) - delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", branch: default_branch, commit_message: 'Remove README.md') - delete delete_file_request.url + delete_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/README.md", branch: default_branch, commit_message: 'Remove README.md') + delete delete_file_request.url - expect_status(204) + expect_status(204) - get_tree_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/tree") - get get_tree_request.url + get_tree_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/tree") + get get_tree_request.url - expect_status(200) - expect(json_body).to eq([]) + expect_status(200) + expect(json_body).to eq([]) - delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}") - delete delete_project_request.url + delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}") + delete delete_project_request.url - expect_status(202) - expect(json_body).to match( - a_hash_including(message: '202 Accepted') - ) - end + expect_status(202) + expect(json_body).to match( + a_hash_including(message: '202 Accepted') + ) + end - describe 'raw file access' do - let(:svg_file) do - <<-SVG + describe 'raw file access' do + let(:svg_file) do + <<-SVG <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> @@ -74,44 +75,45 @@ module QA alert("surprise"); </script> </svg> - SVG - end - - it 'sets no-cache headers as expected', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347746' do - create_project_request = Runtime::API::Request.new(@api_client, '/projects') - post create_project_request.url, path: project_name, name: project_name - - default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] - - create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg") - post create_file_request.url, branch: default_branch, content: svg_file, commit_message: 'Add test.svg' - - get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: default_branch) - - 3.times do - response = get get_file_request.url - - # Subsequent responses aren't cached, so headers should match from - # request to request, especially a 200 response rather than a 304 - # (indicating a cached response.) Further, :content_disposition - # should include `attachment` for all responses. - # - expect(response.headers[:cache_control]).to include("no-store") - expect(response.headers[:cache_control]).to include("no-cache") - expect(response.headers[:pragma]).to eq("no-cache") - expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") - expect(response.headers[:content_disposition]).to include("attachment") - expect(response.headers[:content_disposition]).not_to include("inline") - expect(response.headers[:content_type]).to include("image/svg+xml") + SVG end - delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}") - delete delete_project_request.url - - expect_status(202) - expect(json_body).to match( - a_hash_including(message: '202 Accepted') - ) + it 'sets no-cache headers as expected', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347746' do + create_project_request = Runtime::API::Request.new(@api_client, '/projects') + post create_project_request.url, path: project_name, name: project_name + + default_branch = json_body[:default_branch].to_s.empty? ? Runtime::Env.default_branch : json_body[:default_branch] + + create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg") + post create_file_request.url, branch: default_branch, content: svg_file, commit_message: 'Add test.svg' + + get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: default_branch) + + 3.times do + response = get get_file_request.url + + # Subsequent responses aren't cached, so headers should match from + # request to request, especially a 200 response rather than a 304 + # (indicating a cached response.) Further, :content_disposition + # should include `attachment` for all responses. + # + expect(response.headers[:cache_control]).to include("no-store") + expect(response.headers[:cache_control]).to include("no-cache") + expect(response.headers[:pragma]).to eq("no-cache") + expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") + expect(response.headers[:content_disposition]).to include("attachment") + expect(response.headers[:content_disposition]).not_to include("inline") + expect(response.headers[:content_type]).to include("image/svg+xml") + end + + delete_project_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}") + delete delete_project_request.url + + expect_status(202) + expect(json_body).to match( + a_hash_including(message: '202 Accepted') + ) + end end end end diff --git a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb new file mode 100644 index 00000000000..2b48945137c --- /dev/null +++ b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module QA + # TODO: + # Remove FF :ci_trigger_forward_variables + # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed + RSpec.describe 'Verify', :runner, feature_flag: { + name: 'ci_trigger_forward_variables', + scope: :global + } do + describe 'Pipeline API defined variable inheritance' do + include_context 'variable inheritance test prep' + + before do + add_ci_file(downstream1_project, [downstream1_ci_file]) + add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file, upstream_child2_ci_file]) + + start_pipeline_via_api_with_variable + + Support::Waiter.wait_until(max_duration: 180, sleep_interval: 5) do + upstream_pipeline.status == 'success' + end + + Support::Waiter.wait_until(max_duration: 180, sleep_interval: 5) do + downstream1_pipeline.pipeline_variables && child1_pipeline.pipeline_variables + end + end + + it( + 'is determined based on forward:pipeline_variables condition', + :aggregate_failures, + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360745', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361400', type: :investigating } + ) do + # Is inheritable when true + expect(child1_pipeline).to have_variable(key: key, value: value), + "Expected to find `{key: 'TEST_VAR', value: 'This is great!'}`" \ + " but got #{child1_pipeline.pipeline_variables}" + + # Is not inheritable when false + expect(child2_pipeline).not_to have_variable(key: key, value: value), + "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \ + " but got #{child2_pipeline.pipeline_variables}" + + # Is not inheritable by default + expect(downstream1_pipeline).not_to have_variable(key: key, value: value), + "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \ + " but got #{downstream1_pipeline.pipeline_variables}" + end + + def start_pipeline_via_api_with_variable + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = upstream_project + pipeline.variables = [{ key: key, value: value, variable_type: 'env_var' }] + end + + Support::Waiter.wait_until { upstream_project.pipelines.size > 1 } + end + + def upstream_pipeline + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = upstream_project + pipeline.id = upstream_project.pipelines.first[:id] + end + end + + def child1_pipeline + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = upstream_project + pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'child1_trigger') + end + end + + def child2_pipeline + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = upstream_project + pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'child2_trigger') + end + end + + def downstream1_pipeline + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = downstream1_project + pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'downstream1_trigger') + end + end + + def upstream_ci_file + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + stages: + - test + - deploy + + child1_trigger: + stage: test + trigger: + include: .child1-ci.yml + forward: + pipeline_variables: true + + child2_trigger: + stage: test + trigger: + include: .child2-ci.yml + forward: + pipeline_variables: false + + # default behavior + downstream1_trigger: + stage: deploy + trigger: + project: #{downstream1_project.full_path} + YAML + } + end + end + end +end diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb index 3a1a3ff7169..d7207803d45 100644 --- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb @@ -3,7 +3,7 @@ require 'airborne' module QA - RSpec.describe 'Package', only: { subdomain: %i[staging pre] } do + RSpec.describe 'Package', only: { subdomain: %i[staging pre] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/360466', type: :investigating } do include Support::API describe 'Container Registry' do @@ -57,7 +57,6 @@ module QA MEDIA_TYPE: 'application/vnd.docker.distribution.manifest.v2+json' before_script: - token=$(curl -u "$CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD" "https://$CI_SERVER_HOST/jwt/auth?service=container_registry&scope=repository:$CI_PROJECT_PATH:pull,push,delete" | jq -r '.token') - - echo $token script: - 'digest=$(curl -L -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/manifests/master" | jq -r ".layers[0].digest")' - 'curl -L -X DELETE -H "Authorization: Bearer $token" -H "Accept: $MEDIA_TYPE" "https://$CI_REGISTRY/v2/$CI_PROJECT_PATH/blobs/$digest"' diff --git a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb index bb4e0d71710..72090306fd9 100644 --- a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb +++ b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true module QA - RSpec.describe 'Service ping default enabled' do - context 'When using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do - before do - Flow::Login.sign_in_as_admin + RSpec.describe 'Non-devops' do + describe 'Service ping default enabled' do + context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do + before do + Flow::Login.sign_in_as_admin - Page::Main::Menu.perform(&:go_to_admin_area) - Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) - end + Page::Main::Menu.perform(&:go_to_admin_area) + Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) + end - it 'has service ping toggle enabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335' do - Page::Admin::Settings::MetricsAndProfiling.perform do |setting| - setting.expand_usage_statistics do |page| - expect(page).not_to have_disabled_usage_data_checkbox + it 'has service ping toggle enabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335' do + Page::Admin::Settings::MetricsAndProfiling.perform do |setting| + setting.expand_usage_statistics do |page| + expect(page).not_to have_disabled_usage_data_checkbox + end end end end diff --git a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb index cab8bd367f5..791bd688cea 100644 --- a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb +++ b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true module QA - RSpec.describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do - context 'when disabled from gitlab.yml config' do - before do - Flow::Login.sign_in_as_admin + RSpec.describe 'Non-devops' do + describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do + context 'when disabled from gitlab.yml config' do + before do + Flow::Login.sign_in_as_admin - Page::Main::Menu.perform(&:go_to_admin_area) - Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) - end + Page::Main::Menu.perform(&:go_to_admin_area) + Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) + end - it 'has service ping toggle is disabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340' do - Page::Admin::Settings::MetricsAndProfiling.perform do |settings| - settings.expand_usage_statistics do |usage_statistics| - expect(usage_statistics).to have_disabled_usage_data_checkbox + it 'has service ping toggle is disabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340' do + Page::Admin::Settings::MetricsAndProfiling.perform do |settings| + settings.expand_usage_statistics do |usage_statistics| + expect(usage_statistics).to have_disabled_usage_data_checkbox + end end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 3bf5a11b074..0477a9b8a1f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -46,7 +46,7 @@ module QA import_page.import!(github_repo, group.full_path, imported_project.name) aggregate_failures do - expect(import_page).to have_imported_project(github_repo) + expect(import_page).to have_imported_project(github_repo, wait: 240) # validate button is present instead of navigating to avoid dealing with multiple tabs # which makes the test more complicated expect(import_page).to have_go_to_project_button(github_repo) diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb index c2bd61155b1..381a25a14d0 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb @@ -2,8 +2,8 @@ module QA RSpec.describe 'Manage' do - describe 'Project access tokens' do - let(:project_access_token) {QA::Resource::ProjectAccessToken.fabricate_via_browser_ui!} + describe 'Project access tokens', :reliable do + let(:project_access_token) { QA::Resource::ProjectAccessToken.fabricate_via_browser_ui! } it 'can be created and revoked via the UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347688' do expect(project_access_token.token).not_to be_nil 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 eb4eb1d3e71..5fc9f8c2f41 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # TODO: Remove :requires_admin when the `Runtime::Feature.enable` method call is removed - RSpec.describe 'Plan', :smoke, :requires_admin do + RSpec.describe 'Plan', :smoke, feature_flag: { name: 'vue_issues_list', scope: :group } do describe 'Issue creation' do let(:project) { Resource::Project.fabricate_via_api! } let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } } diff --git a/qa/qa/specs/features/browser_ui/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 b7284f972ef..5f896c7bf10 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 @@ -12,7 +12,7 @@ module QA Flow::Login.sign_in end - it 'user adds a design and annotates it', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/352746', type: :investigating }, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do + it 'user adds a design and annotates it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do issue.visit! Page::Project::Issue::Show.perform do |issue| diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_from_push_notification_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_from_push_notification_spec.rb new file mode 100644 index 00000000000..10d7e0b071f --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_from_push_notification_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Create a new merge request from the event notification after a push' do + let(:branch_name) { "merge-request-test-#{SecureRandom.hex(8)}" } + let(:title) { "Merge from push event notification test #{SecureRandom.hex(8)}" } + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.initialize_with_readme = true + end + end + + before do + Flow::Login.sign_in + end + + it( + 'creates a merge request after a push via the git CLI', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360489' + ) do + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.branch_name = branch_name + end + + project.visit! + Page::Project::Show.perform(&:new_merge_request) + Page::MergeRequest::New.perform do |merge_request| + merge_request.fill_title(title) + merge_request.create_merge_request + end + + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_title(title) + end + end + + it( + 'creates a merge request after a push via the API', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360490' + ) do + commit = Resource::Repository::Commit.fabricate_via_api! do |resource| + resource.project = project + resource.add_files([{ 'file_path': "file-#{SecureRandom.hex(8)}.txt", 'content': 'MR init' }]) + resource.branch = branch_name + resource.start_branch = project.default_branch + end + project.wait_for_push(commit.commit_message) + + project.visit! + Page::Project::Show.perform(&:new_merge_request) + Page::MergeRequest::New.perform do |merge_request| + merge_request.fill_title(title) + merge_request.create_merge_request + end + + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_title(title) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 6a2fe705cf7..f4ed9f28dac 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -24,7 +24,6 @@ module QA Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| merge_request.project = project merge_request.title = merge_request_title - merge_request.assignee = 'me' merge_request.description = merge_request_description end @@ -38,7 +37,7 @@ module QA 'creates a merge request with a milestone and label', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347762' ) do - gitlab_account_username = "@#{Runtime::User.username}" + gitlab_account_user_name = Resource::User.default.reload!.name milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone| milestone.project = project @@ -54,14 +53,13 @@ module QA merge_request.description = merge_request_description merge_request.project = project merge_request.milestone = milestone - merge_request.assignee = 'me' merge_request.labels.push(label) end Page::MergeRequest::Show.perform do |merge_request| expect(merge_request).to have_title(merge_request_title) expect(merge_request).to have_description(merge_request_description) - expect(merge_request).to have_assignee(gitlab_account_username) + expect(merge_request).to have_assignee(gitlab_account_user_name) expect(merge_request).to have_label(label.title) expect(merge_request).to have_milestone(milestone.title) end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index d1e852979d0..b544c9aa211 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -5,7 +5,7 @@ module QA describe 'Merge request creation from fork', quarantine: { only: :production, issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/343801", - type: :investigation + type: :investigating } do let(:merge_request) do Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request| diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb index 38adf2f5d55..b20d0929e9c 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', :reliable do context 'Add batch suggestions to a Merge Request' do let(:project) do Resource::Project.fabricate_via_api! do |project| 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 812a7bae6c3..0e4aa67162f 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 @@ -1,32 +1,27 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :requires_admin do + # This test modifies an instance level setting, + # so skipping on live envs to avoid random transient issues + RSpec.describe 'Create', :requires_admin, :skip_live_env do describe 'push after setting the file size limit via admin/application_settings' do - # Note: The file size limits in this test should be greater than the limits in - # ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from - # triggering the limit set in this test (which can happen on Staging where the - # tests are run in parallel). - # See: https://gitlab.com/gitlab-org/gitlab/-/issues/218620#note_361634705 - include Support::API - before(:context) do - @project = Resource::Project.fabricate_via_api! do |p| + let!(:project) do + Resource::Project.fabricate_via_api! do |p| p.name = 'project-test-push-limit' p.initialize_with_readme = true end - - @api_client = Runtime::API::Client.as_admin end after(:context) do - # need to set the default value after test - # default value for file size limit is empty set_file_size_limit(nil) end - it 'push successful when the file size is under the limit', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347758' do + it( + 'push successful when the file size is under the limit', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347758' + ) do set_file_size_limit(5) retry_on_fail do @@ -36,7 +31,10 @@ module QA end end - it 'push fails when the file size is above the limit', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347759' do + it( + 'push fails when the file size is above the limit', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347759' + ) do set_file_size_limit(2) retry_on_fail do @@ -46,7 +44,7 @@ module QA end def set_file_size_limit(limit) - request = Runtime::API::Request.new(@api_client, '/application/settings') + request = Runtime::API::Request.new(Runtime::API::Client.as_admin, '/application/settings') response = put request.url, receive_max_input_size: limit expect(response.code).to eq(200) @@ -56,13 +54,13 @@ module QA def push_new_file(file_name, wait_for_push: true) commit_message = 'Adding a new file' output = Resource::Repository::Push.fabricate! do |p| - p.repository_http_uri = @project.repository_http_location.uri + p.repository_http_uri = project.repository_http_location.uri p.file_name = file_name p.file_content = SecureRandom.random_bytes(3000000) p.commit_message = commit_message p.new_branch = false end - @project.wait_for_push commit_message + project.wait_for_push commit_message output end @@ -77,10 +75,8 @@ module QA # under a minute, i.e., in fewer than 6 attempts with a 10 second sleep # between attempts. # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/30233#note_188616863 - def retry_on_fail - Support::Retrier.retry_on_exception(max_attempts: 6, reload_page: nil, sleep_interval: 10) do - yield - end + def retry_on_fail(&block) + Support::Retrier.retry_on_exception(max_attempts: 6, reload_page: false, sleep_interval: 10, &block) 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 f8129c9ccba..91b0940f137 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', :reliable do describe 'Protected branch support' do let(:branch_name) { 'protected-branch' } let(:commit_message) { 'Protected push commit message' } diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb index b0eb3ac7b37..a1a795ce0f3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb @@ -1,34 +1,36 @@ # frozen_string_literal: true module QA - RSpec.describe 'SSH keys support', :smoke, :skip_fips_env do - key_title = "key for ssh tests #{Time.now.to_f}" - key = nil + RSpec.describe 'Create' do + describe 'SSH keys support', :smoke, :skip_fips_env do + key_title = "key for ssh tests #{Time.now.to_f}" + key = nil - before do - Flow::Login.sign_in - end + before do + Flow::Login.sign_in + end + + it 'user can add an SSH key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347819' do + key = Resource::SSHKey.fabricate_via_browser_ui! do |resource| + resource.title = key_title + end - it 'user can add an SSH key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347819' do - key = Resource::SSHKey.fabricate_via_browser_ui! do |resource| - resource.title = key_title + expect(page).to have_content(key.title) + expect(page).to have_content(key.md5_fingerprint) end - expect(page).to have_content(key.title) - expect(page).to have_content(key.md5_fingerprint) - end + # Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context. + context 'after adding an ssh key' do + it 'can delete an ssh key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347820' do + Page::Main::Menu.perform(&:click_edit_profile_link) + Page::Profile::Menu.perform(&:click_ssh_keys) + Page::Profile::SSHKeys.perform do |ssh_keys| + ssh_keys.remove_key(key.title) + end - # Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context. - context 'after adding an ssh key' do - it 'can delete an ssh key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347820' do - Page::Main::Menu.perform(&:click_edit_profile_link) - Page::Profile::Menu.perform(&:click_ssh_keys) - Page::Profile::SSHKeys.perform do |ssh_keys| - ssh_keys.remove_key(key.title) + expect(page).not_to have_content("Title: #{key.title}") + expect(page).not_to have_content(key.md5_fingerprint) end - - expect(page).not_to have_content("Title: #{key.title}") - expect(page).not_to have_content(key.md5_fingerprint) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb index 0b63d9a1edb..a50b995e483 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', :reliable do describe 'Multiple file snippet' do let(:first_file_content) { 'First file content' } let(:second_file_content) { 'Second file content' } diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb index b45624381c8..4b8edf8b792 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', :reliable do context 'Content Editor' do let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } let(:page_title) { 'Content Editor Page' } diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb new file mode 100644 index 00000000000..63801536c34 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify', :runner do + describe 'Pipeline with customizable variable' do + let(:executor) { "qa-runner-#{Time.now.to_i}" } + let(:pipeline_job_name) { 'customizable-variable' } + let(:variable_custom_value) { 'Custom Foo' } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-customizable-variable-pipeline' + end + end + + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = executor + runner.tags = [executor] + end + end + + let!(:commit) 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 + variables: + FOO: + value: "Default Foo" + description: "This is a description for the foo variable" + #{pipeline_job_name}: + tags: + - #{executor} + script: echo "$FOO" + YAML + } + ] + ) + end + end + + before do + Flow::Login.sign_in + project.visit! + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform do |index| + index.click_run_pipeline_button + end + end + + after do + [runner, project].each(&:remove_via_api!) + end + + it( + 'manually creates a pipeline and uses the defined custom variable value', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/361814' + ) do + Page::Project::Pipeline::New.perform do |new| + new.configure_variable(value: variable_custom_value) + new.click_run_pipeline_button + end + + Page::Project::Pipeline::Show.perform do |show| + Support::Waiter.wait_until { show.passed? } + end + + job = Resource::Job.fabricate_via_api! do |job| + job.id = project.job_by_name(pipeline_job_name)[:id] + job.name = pipeline_job_name + job.project = project + end + + job.visit! + + Page::Project::Job::Show.perform do |show| + expect(show.output).to have_content(variable_custom_value) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb index 496cc5f8a60..bd200e57ff9 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb @@ -24,7 +24,8 @@ module QA it( 'is inheritable when forward:pipeline_variables is true', :aggregate_failures, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358197' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358197', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361338', type: :investigating } ) do visit_job_page('child1', 'child1_job') verify_job_log_shows_variable_value diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb index 2a0aaf6d7a3..2bd0be542fe 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb @@ -25,7 +25,8 @@ module QA it( 'is not inheritable when forward:pipeline_variables is false', :aggregate_failures, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358199' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358199', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361339', type: :investigating } ) do visit_job_page('child1', 'child1_job') verify_job_log_does_not_show_variable_value @@ -39,7 +40,8 @@ module QA it( 'is not inheritable by default', :aggregate_failures, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358200' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358200', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361339', type: :investigating } ) do visit_job_page('child2', 'child2_job') verify_job_log_does_not_show_variable_value diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb index 0d8756fc9a3..c5408f30d16 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb @@ -44,7 +44,7 @@ module QA end it( - 'creates a multi-project pipeline', + 'creates a multi-project pipeline with artifact download', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358064' ) do Page::Project::Pipeline::Show.perform do |show| @@ -78,7 +78,10 @@ module QA job1: stage: test tags: ["#{executor}"] - script: echo 'done' + script: echo 'done' > output.txt + artifacts: + paths: + - output.txt staging: stage: deploy @@ -96,7 +99,12 @@ module QA "#{downstream_job_name}": stage: test tags: ["#{executor}"] - script: echo 'done' + needs: + - project: #{upstream_project.path_with_namespace} + job: job1 + ref: main + artifacts: true + script: cat output.txt YAML } end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb index 5b7a569fa8a..f2278c7bf6d 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb @@ -60,7 +60,14 @@ module QA child_job: stage: test tags: ["#{project.name}"] - script: echo "Child job done!" + needs: + - project: #{project.path_with_namespace} + job: job1 + ref: main + artifacts: true + script: + - cat output.txt + - echo "Child job done!" YAML } @@ -84,18 +91,28 @@ module QA file_path: '.gitlab-ci.yml', content: <<~YAML stages: + - build - test - deploy + default: + tags: ["#{project.name}"] + job1: + stage: build + script: echo "build success" > output.txt + artifacts: + paths: + - output.txt + + job2: stage: test trigger: include: ".child-ci.yml" strategy: depend - job2: + job3: stage: deploy - tags: ["#{project.name}"] script: echo "parent deploy done" YAML diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb index 8d2af3ea0df..ac91a9dd2d3 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb @@ -73,7 +73,7 @@ module QA show.select_branch_from_dropdown(random_test_string) aggregate_failures do - expect(show.target_branch_name).to eq(random_test_string), 'Target branch field is not showing expected branch name.' + expect(show.source_branch_name).to eq(random_test_string), 'Branch field is not showing expected branch name.' expect(show.editing_content).to have_content(random_test_string), 'Editor content does not include expected test string.' end @@ -81,7 +81,7 @@ module QA show.select_branch_from_dropdown(project.default_branch) aggregate_failures do - expect(show.target_branch_name).to eq(project.default_branch), 'Target branch field is not showing expected branch name.' + expect(show.source_branch_name).to eq(project.default_branch), 'Branch field is not showing expected branch name.' expect(show.editing_content).to have_content(project.default_branch), 'Editor content does not include expected test string.' end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb index b9f616aa733..931bb97ba32 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb @@ -34,8 +34,8 @@ module QA expect(show).to have_no_new_mr_checkbox end - # The new MR checkbox is visible after a new target branch name is set - show.set_target_branch(SecureRandom.hex(10)) + # The new MR checkbox is visible after a new branch name is set + show.set_source_branch(SecureRandom.hex(10)) expect(show).to have_new_mr_checkbox show.select_new_mr_checkbox diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb index 205b4d1168a..d03ebd5aba3 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb @@ -31,7 +31,7 @@ module QA project.remove_via_api! end - it 'creates 2 trigger jobs and passes corresponding matrix variables', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348000' do + it 'creates 2 trigger jobs and passes corresponding matrix variables', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348000', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361346', type: :investigating } do Page::Project::Pipeline::Show.perform do |parent_pipeline| trigger_title1 = 'deploy: [ovh, monitoring]' trigger_title2 = 'deploy: [ovh, app]' diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb index 00c5d4c74d4..d34df17c477 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb @@ -53,13 +53,13 @@ module QA it 'creates new pipeline and target branch', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349005' do Page::Project::PipelineEditor::Show.perform do |show| show.write_to_editor(random_test_string) - show.set_target_branch(random_test_string) + show.set_source_branch(random_test_string) show.submit_changes Support::Waiter.wait_until { project.pipelines.size > 1 } aggregate_failures do - expect(show.target_branch_name).to eq(random_test_string) + expect(show.source_branch_name).to eq(random_test_string) expect(show.current_branch).to eq(random_test_string) expect(show.editing_content).to have_content(random_test_string) expect { show.pipeline_id }.to eventually_eq(project.pipelines.pluck(:id).max).within(max_duration: 60, sleep_interval: 3) 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 f44c56ca0f9..122fb0fc1a0 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 @@ -3,7 +3,6 @@ module QA RSpec.describe 'Verify', :runner do describe 'Code coverage statistics' do - let(:simplecov) { '\(\d+.\d+\%\) covered' } let(:executor) { "qa-runner-#{Time.now.to_i}" } let(:runner) do Resource::Runner.fabricate_via_api! do |runner| @@ -19,8 +18,9 @@ module QA mr.file_content = <<~EOF test: tags: [e2e-test] + coverage: '/\\d+\\.\\d+% covered/' script: - - echo '(66.67%) covered' + - echo '66.67% covered' EOF end end @@ -34,8 +34,6 @@ module QA end it 'creates an MR with code coverage statistics', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348068' do - runner.project.visit! - configure_code_coverage(simplecov) merge_request.visit! Page::MergeRequest::Show.perform do |mr_widget| @@ -44,16 +42,5 @@ module QA end end end - - private - - def configure_code_coverage(coverage_tool_pattern) - Page::Project::Menu.perform(&:go_to_ci_cd_settings) - Page::Project::Settings::CiCd.perform do |settings| - settings.expand_general_pipelines do |coverage| - coverage.configure_coverage_regex(coverage_tool_pattern) - end - end - end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb index 9a5a5416d5c..d9d75a8ae7a 100644 --- a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb @@ -39,6 +39,7 @@ module QA end after do + project.remove_via_api! runner.remove_via_api! end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb index 9ef5b8c84fa..921b36b34af 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable do describe 'Maven group level endpoint' do include Runtime::Fixtures include_context 'packages registry qa scenario' diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb index d79f65764d4..13607ba1b41 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable do describe 'Maven project level endpoint' do let(:group_id) { 'com.gitlab.qa' } let(:artifact_id) { "maven-#{SecureRandom.hex(8)}" } diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb index 0ee5f1b6a0b..61a92daf129 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb @@ -1,170 +1,170 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do - describe 'npm instance level endpoint' do - using RSpec::Parameterized::TableSyntax - include Runtime::Fixtures - include Support::Helpers::MaskToken - - let!(:registry_scope) { Runtime::Namespace.sandbox_name } - let!(:personal_access_token) do - unless Page::Main::Menu.perform(&:signed_in?) - Flow::Login.sign_in + RSpec.describe 'Package' do + describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do + describe 'npm instance level endpoint' do + using RSpec::Parameterized::TableSyntax + include Runtime::Fixtures + include Support::Helpers::MaskToken + + let!(:registry_scope) { Runtime::Namespace.sandbox_name } + let!(:personal_access_token) do + Flow::Login.sign_in unless Page::Main::Menu.perform(&:signed_in?) + + Resource::PersonalAccessToken.fabricate!.token end - Resource::PersonalAccessToken.fabricate!.token - end - - let(:project_deploy_token) do - Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token| - deploy_token.name = 'npm-deploy-token' - deploy_token.project = project - deploy_token.scopes = %w[ - read_repository - read_package_registry - write_package_registry - ] + let(:project_deploy_token) do + Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token| + deploy_token.name = 'npm-deploy-token' + deploy_token.project = project + deploy_token.scopes = %w[ + read_repository + read_package_registry + write_package_registry + ] + end end - end - let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) } - let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" } - let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" } + let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) } + let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" } + let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" } - let!(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'npm-instace-level-publish' + let!(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'npm-instace-level-publish' + end end - end - let!(:another_project) do - Resource::Project.fabricate_via_api! do |another_project| - another_project.name = 'npm-instance-level-install' - another_project.template_name = 'express' - another_project.group = project.group + let!(:another_project) do + Resource::Project.fabricate_via_api! do |another_project| + another_project.name = 'npm-instance-level-install' + another_project.template_name = 'express' + another_project.group = project.group + end end - end - let!(:runner) do - Resource::Runner.fabricate! do |runner| - runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = ["runner-for-#{project.group.name}"] - runner.executor = :docker - runner.token = project.group.reload!.runners_token + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.name = "qa-runner-#{Time.now.to_i}" + runner.tags = ["runner-for-#{project.group.name}"] + runner.executor = :docker + runner.token = project.group.reload!.runners_token + end end - end - let(:package) do - Resource::Package.init do |package| - package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}" - package.project = project + let(:package) do + Resource::Package.init do |package| + package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}" + package.project = project + end end - end - - after do - package.remove_via_api! - runner.remove_via_api! - project.remove_via_api! - another_project.remove_via_api! - end - where(:case_name, :authentication_token_type, :token_name, :testcase) do - 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347600' - 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347599' - 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347598' - end + after do + package.remove_via_api! + runner.remove_via_api! + project.remove_via_api! + another_project.remove_via_api! + end - with_them do - let(:auth_token) do - case authentication_token_type - when :personal_access_token - use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project) - use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: another_project) - when :ci_job_token - '${CI_JOB_TOKEN}' - when :project_deploy_token - use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project) - use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: another_project) - end + where(:case_name, :authentication_token_type, :token_name, :testcase) do + 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347600' + 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347599' + 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347598' end - it 'push and pull a npm package via CI', testcase: params[:testcase] do - Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do - npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding) - package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding) - - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = project - commit.commit_message = 'Add files' - commit.add_files([ - { - file_path: '.gitlab-ci.yml', - content: npm_upload_yaml - }, - { - file_path: 'package.json', - content: package_json - } - ]) + with_them do + let(:auth_token) do + case authentication_token_type + when :personal_access_token + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project) + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: another_project) + when :ci_job_token + '${CI_JOB_TOKEN}' + when :project_deploy_token + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project) + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: another_project) end end - project.visit! - Flow::Pipeline.visit_latest_pipeline + it 'push and pull a npm package via CI', testcase: params[:testcase] do + Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding) + + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add files' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: npm_upload_yaml + }, + { + file_path: 'package.json', + content: package_json + } + ]) + end + end - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('deploy') - end + project.visit! + Flow::Pipeline.visit_latest_pipeline - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - end + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('deploy') + end - Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do - Resource::Repository::Commit.fabricate_via_api! do |commit| - npm_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_install_package_instance.yaml.erb')).result(binding) - - commit.project = another_project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([ - { - file_path: '.gitlab-ci.yml', - content: npm_install_yaml - } - ]) + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) end - end - another_project.visit! - Flow::Pipeline.visit_latest_pipeline + Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + npm_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_install_package_instance.yaml.erb')).result(binding) + + commit.project = another_project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: npm_install_yaml + } + ]) + end + end - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('install') - end + another_project.visit! + Flow::Pipeline.visit_latest_pipeline - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - job.click_browse_button - end + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('install') + end - Page::Project::Artifact::Show.perform do |artifacts| - artifacts.go_to_directory('node_modules') - artifacts.go_to_directory("@#{registry_scope}") - expect(artifacts).to have_content("#{project.name}") - end + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + job.click_browse_button + end + + Page::Project::Artifact::Show.perform do |artifacts| + artifacts.go_to_directory('node_modules') + artifacts.go_to_directory("@#{registry_scope}") + expect(artifacts).to have_content(project.name.to_s) + end - project.visit! - Page::Project::Menu.perform(&:click_packages_link) + project.visit! + Page::Project::Menu.perform(&:click_packages_link) - Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package.name) + Page::Project::Packages::Index.perform do |index| + expect(index).to have_package(package.name) - index.click_package(package.name) - end + index.click_package(package.name) + end - Page::Project::Packages::Show.perform do |show| - expect(show).to have_package_info(package.name, "1.0.0") + Page::Project::Packages::Show.perform do |show| + expect(show).to have_package_info(package.name, "1.0.0") + end end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb index 5ebcb94d0d0..59324c7338a 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb @@ -1,142 +1,142 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do - describe 'npm project level endpoint' do - using RSpec::Parameterized::TableSyntax - include Runtime::Fixtures - include Support::Helpers::MaskToken - - let!(:registry_scope) { Runtime::Namespace.sandbox_name } - let!(:personal_access_token) do - unless Page::Main::Menu.perform(&:signed_in?) - Flow::Login.sign_in + RSpec.describe 'Package' do + describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do + describe 'npm project level endpoint' do + using RSpec::Parameterized::TableSyntax + include Runtime::Fixtures + include Support::Helpers::MaskToken + + let!(:registry_scope) { Runtime::Namespace.sandbox_name } + let!(:personal_access_token) do + Flow::Login.sign_in unless Page::Main::Menu.perform(&:signed_in?) + + Resource::PersonalAccessToken.fabricate!.token end - Resource::PersonalAccessToken.fabricate!.token - end - - let(:project_deploy_token) do - Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token| - deploy_token.name = 'npm-deploy-token' - deploy_token.project = project - deploy_token.scopes = %w[ - read_repository - read_package_registry - write_package_registry - ] - end - end - - let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) } - let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" } - let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" } - - let!(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'npm-project-level' - project.visibility = :private - end - end - - let!(:runner) do - Resource::Runner.fabricate! do |runner| - runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = ["runner-for-#{project.name}"] - runner.executor = :docker - runner.project = project - end - end - - let(:package) do - Resource::Package.init do |package| - package.name = "@#{registry_scope}/mypackage-#{SecureRandom.hex(8)}" - package.project = project - end - end - - after do - package.remove_via_api! - runner.remove_via_api! - project.remove_via_api! - end - - where(:case_name, :authentication_token_type, :token_name, :testcase) do - 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347592' - 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347594' - 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347593' - end - - with_them do - let(:auth_token) do - case authentication_token_type - when :personal_access_token - use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project) - when :ci_job_token - '${CI_JOB_TOKEN}' - when :project_deploy_token - use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project) + let(:project_deploy_token) do + Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token| + deploy_token.name = 'npm-deploy-token' + deploy_token.project = project + deploy_token.scopes = %w[ + read_repository + read_package_registry + write_package_registry + ] end end - it 'push and pull a npm package via CI', testcase: params[:testcase] do - Resource::Repository::Commit.fabricate_via_api! do |commit| - npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding) - package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding) - - commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([ - { - file_path: '.gitlab-ci.yml', - content: npm_upload_install_yaml - }, - { - file_path: 'package.json', - content: package_json - } - ]) - end - - project.visit! - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('deploy') - end + let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) } + let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" } + let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" } - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - end - - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('install') + let!(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'npm-project-level' + project.visibility = :private end + end - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - job.click_browse_button + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.name = "qa-runner-#{Time.now.to_i}" + runner.tags = ["runner-for-#{project.name}"] + runner.executor = :docker + runner.project = project end + end - Page::Project::Artifact::Show.perform do |artifacts| - artifacts.go_to_directory('node_modules') - artifacts.go_to_directory("@#{registry_scope}") - expect(artifacts).to have_content('mypackage') + let(:package) do + Resource::Package.init do |package| + package.name = "@#{registry_scope}/mypackage-#{SecureRandom.hex(8)}" + package.project = project end + end - project.visit! - Page::Project::Menu.perform(&:click_packages_link) + after do + package.remove_via_api! + runner.remove_via_api! + project.remove_via_api! + end - Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package.name) + where(:case_name, :authentication_token_type, :token_name, :testcase) do + 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347592' + 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347594' + 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347593' + end - index.click_package(package.name) + with_them do + let(:auth_token) do + case authentication_token_type + when :personal_access_token + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project) + when :ci_job_token + '${CI_JOB_TOKEN}' + when :project_deploy_token + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project) + end end - Page::Project::Packages::Show.perform do |show| - expect(show).to have_package_info(package.name, "1.0.0") + it 'push and pull a npm package via CI', testcase: params[:testcase] do + Resource::Repository::Commit.fabricate_via_api! do |commit| + npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding) + + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: npm_upload_install_yaml + }, + { + file_path: 'package.json', + content: package_json + } + ]) + end + + project.visit! + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('deploy') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end + + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('install') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + job.click_browse_button + end + + Page::Project::Artifact::Show.perform do |artifacts| + artifacts.go_to_directory('node_modules') + artifacts.go_to_directory("@#{registry_scope}") + expect(artifacts).to have_content('mypackage') + end + + project.visit! + Page::Project::Menu.perform(&:click_packages_link) + + Page::Project::Packages::Index.perform do |index| + expect(index).to have_package(package.name) + + index.click_package(package.name) + end + + Page::Project::Packages::Show.perform do |show| + expect(show).to have_package_info(package.name, "1.0.0") + end end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb index 0ddb59d6625..f229f30facc 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable do describe 'NuGet group level endpoint' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb index d5fd78480d2..ab6896ca26f 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable, quarantine: { + type: :flaky, + issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/361704" + } do describe 'NuGet project level endpoint' do include Support::Helpers::MaskToken @@ -129,12 +132,12 @@ module QA file_path: 'dotnetcore.csproj', content: <<~EOF <Project Sdk="Microsoft.NET.Sdk"> - + <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> - + </Project> EOF } 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 e0cd5a52bfb..dca6f961047 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 @@ -1,7 +1,12 @@ # frozen_string_literal: true module QA - RSpec.describe 'Configure', only: { subdomain: :staging } do + RSpec.describe 'Configure', + only: { subdomain: :staging }, + quarantine: { + issue: 'https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1198', + type: :waiting_on + } do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'autodevops-project' diff --git a/qa/spec/support/shared_contexts/merge_train_spec_with_user_prep.rb b/qa/qa/specs/features/shared_contexts/merge_train_spec_with_user_prep.rb index 9d1a37cb0b8..9d1a37cb0b8 100644 --- a/qa/spec/support/shared_contexts/merge_train_spec_with_user_prep.rb +++ b/qa/qa/specs/features/shared_contexts/merge_train_spec_with_user_prep.rb diff --git a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb b/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb index 73a6c2bd99e..73a6c2bd99e 100644 --- a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/packages_registry_shared_context.rb diff --git a/qa/spec/support/shared_contexts/variable_inheritance_shared_context.rb b/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb index 1dc8870d4d9..fbe517f51f8 100644 --- a/qa/spec/support/shared_contexts/variable_inheritance_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb @@ -2,6 +2,8 @@ module QA RSpec.shared_context 'variable inheritance test prep' do + let(:key) { 'TEST_VAR' } + let(:value) { 'This is great!' } let(:random_string) { Faker::Alphanumeric.alphanumeric(number: 8) } let(:group) do @@ -57,7 +59,7 @@ module QA Flow::Pipeline.wait_for_latest_pipeline Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) Page::Project::Pipeline::New.perform do |new| - new.add_variable('TEST_VAR', 'This is great!') + new.configure_variable(key: key, value: value) new.click_run_pipeline_button end end @@ -80,14 +82,14 @@ module QA def verify_job_log_shows_variable_value Page::Project::Job::Show.perform do |show| show.wait_until { show.successful? } - expect(show.output).to have_content('This is great!') + expect(show.output).to have_content(value) end end def verify_job_log_does_not_show_variable_value Page::Project::Job::Show.perform do |show| show.wait_until { show.successful? } - expect(show.output).to have_no_content('This is great!') + expect(show.output).to have_no_content(value) end end diff --git a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb b/qa/qa/specs/features/shared_examples/merge_with_code_owner_shared_examples.rb index 4bbad9bf3e5..4bbad9bf3e5 100644 --- a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb +++ b/qa/qa/specs/features/shared_examples/merge_with_code_owner_shared_examples.rb diff --git a/qa/qa/specs/knapsack_runner.rb b/qa/qa/specs/knapsack_runner.rb new file mode 100644 index 00000000000..4908553e43d --- /dev/null +++ b/qa/qa/specs/knapsack_runner.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Specs + class KnapsackRunner + def self.run(args) + allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator + + Knapsack.logger.info '==== Knapsack specs to execute =====' + Knapsack.logger.info 'Report specs:' + Knapsack.logger.info allocator.report_node_tests + Knapsack.logger.info 'Leftover specs:' + Knapsack.logger.info allocator.leftover_node_tests + + status = RSpec::Core::Runner.run([*args, '--', *allocator.node_tests]) + yield status if block_given? + status + end + end + end +end diff --git a/qa/spec/qa_deprecation_toolkit_env.rb b/qa/qa/specs/qa_deprecation_toolkit_env.rb index 2a21961d89e..21ef5a6f229 100644 --- a/qa/spec/qa_deprecation_toolkit_env.rb +++ b/qa/qa/specs/qa_deprecation_toolkit_env.rb @@ -7,9 +7,11 @@ require 'active_support/gem_version' module QaDeprecationToolkitEnv # Taken from https://github.com/jeremyevans/ruby-warning/blob/1.1.0/lib/warning.rb#L18 + # rubocop:disable Layout/LineLength def self.kwargs_warning %r{warning: (?:Using the last argument (?:for `.+' )?as keyword parameters is deprecated; maybe \*\* should be added to the call|Passing the keyword argument (?:for `.+' )?as the last hash parameter is deprecated|Splitting the last argument (?:for `.+' )?into positional and keyword parameters is deprecated|The called method (?:`.+' )?is defined here)\n\z} end + # rubocop:enable Layout/LineLength def self.configure! # Enable ruby deprecations for keywords, it's suppressed by default in Ruby 2.7 diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index c30e5d822c4..68b624b3f2e 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -19,18 +19,6 @@ module QA @options = [] end - def paths_from_knapsack - allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator - - QA::Runtime::Logger.info '==== Knapsack specs to execute =====' - QA::Runtime::Logger.info 'Report specs:' - QA::Runtime::Logger.info allocator.report_node_tests.join(', ') - QA::Runtime::Logger.info 'Leftover specs:' - QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ') - - ['--', allocator.node_tests] - end - def rspec_tags tags_for_rspec = [] @@ -52,77 +40,79 @@ module QA tags_for_rspec end - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/CyclomaticComplexity def perform args = [] args.push('--tty') if tty args.push(rspec_tags) args.push(options) - if Runtime::Env.knapsack? - args.push(paths_from_knapsack) - else - args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } + unless Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') } + args.push(DEFAULT_TEST_PATH_ARGS) end - Runtime::Scenario.define(:large_setup?, args.flatten.include?('can_use_large_setup')) - - if Runtime::Scenario.attributes[:parallel] + if Runtime::Env.knapsack? + KnapsackRunner.run(args.flatten) { |status| abort if status.nonzero? } + elsif Runtime::Scenario.attributes[:parallel] ParallelRunner.run(args.flatten) elsif Runtime::Scenario.attributes[:loop] LoopRunner.run(args.flatten) elsif Runtime::Scenario.attributes[:count_examples_only] - args.unshift('--dry-run') - out = StringIO.new + count_examples_only(args) + elsif Runtime::Scenario.attributes[:test_metadata_only] + test_metadata_only(args) + else + RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap { |status| abort if status.nonzero? } + end + end - RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status| - abort if status.nonzero? - end + private - begin - total_examples = out.string.match(/(\d+) examples?,/)[1] - rescue StandardError - raise RegexMismatchError, 'Rspec output did not match regex' - end + def count_examples_only(args) + args.unshift('--dry-run') + out = StringIO.new - filename = build_filename + RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status| + abort if status.nonzero? + end - File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0 + begin + total_examples = out.string.match(/(\d+) examples?,/)[1] + rescue StandardError + raise RegexMismatchError, 'Rspec output did not match regex' + end - $stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''}" - elsif Runtime::Scenario.attributes[:test_metadata_only] - args.unshift('--dry-run') + filename = build_filename - output_file = Pathname.new(File.join(Runtime::Path.qa_root, 'tmp', 'test-metadata.json')) + File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0 - RSpec.configure do |config| - config.add_formatter(QA::Support::JsonFormatter, output_file) - config.fail_if_no_examples = true - end + saved_file_msg = total_examples.to_i > 0 ? ". Saved to file: #{filename}" : '' + $stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{saved_file_msg}" + end - RSpec::Core::Runner.run(args.flatten, $stderr, $stdout) do |status| - abort if status.nonzero? - end + def test_metadata_only(args) + args.unshift('--dry-run') - $stdout.puts "Saved to file: #{output_file}" - else - RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status| - abort if status.nonzero? - end + output_file = Pathname.new(File.join(Runtime::Path.qa_root, 'tmp', 'test-metadata.json')) + + RSpec.configure do |config| + config.add_formatter(QA::Support::JsonFormatter, output_file) + config.fail_if_no_examples = true end - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - private + RSpec::Core::Runner.run(args.flatten, $stderr, $stdout) do |status| + abort if status.nonzero? + end + + $stdout.puts "Saved to file: #{output_file}" + end def build_filename filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase tags = [] + tag_opts = %w[--tag -t] options.reduce do |before, after| - tags << after if %w[--tag -t].include?(before) + tags << after if tag_opts.include?(before) after end tags = tags.compact.join('_') diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb new file mode 100644 index 00000000000..b130fff0488 --- /dev/null +++ b/qa/qa/specs/spec_helper.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require_relative '../../qa' + +require_relative 'qa_deprecation_toolkit_env' +QaDeprecationToolkitEnv.configure! + +Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack? + +QA::Support::GitlabAddress.define_gitlab_address_attribute! +QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run +QA::Runtime::AllureReport.configure! +QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) + +Dir[::File.join(__dir__, "features/shared_examples/*.rb")].sort.each { |f| require f } +Dir[::File.join(__dir__, "features/shared_contexts/*.rb")].sort.each { |f| require f } + +RSpec.configure do |config| + config.include QA::Support::Matchers::EventuallyMatcher + config.include QA::Support::Matchers::HaveMatcher + + config.add_formatter QA::Support::Formatters::ContextFormatter + config.add_formatter QA::Support::Formatters::QuarantineFormatter + config.add_formatter QA::Support::Formatters::FeatureFlagFormatter + config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics? + + config.before(:suite) do |suite| + QA::Resource::ReusableCollection.register_resource_classes do |collection| + QA::Resource::ReusableProject.register(collection) + QA::Resource::ReusableGroup.register(collection) + end + end + + config.prepend_before do |example| + QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}") + QA::Runtime::Example.current = example + + # Reset fabrication counters tracked in resource base + Thread.current[:api_fabrication] = 0 + Thread.current[:browser_ui_fabrication] = 0 + end + + config.after do + # If a .netrc file was created during the test, delete it so that subsequent tests don't try to use the same logins + QA::Git::Repository.new.delete_netrc + end + + # Add fabrication time to spec metadata + config.append_after do |example| + example.metadata[:api_fabrication] = Thread.current[:api_fabrication] + example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication] + end + + config.after(:context) do + if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?) + QA::Page::Main::Menu.perform(&:sign_out) + raise( + <<~ERROR + The test left the browser signed in. + + Usually, Capybara prevents this from happening but some things can + interfere. For example, if it has an `after(:context)` block that logs + in, the browser will stay logged in and this will cause the next test + to fail. + + Please make sure the test does not leave the browser signed in. + ERROR + ) + end + end + + config.after(:suite) do |suite| + # Write all test created resources to JSON file + QA::Tools::TestResourceDataProcessor.write_to_file(suite.reporter.failed_examples.any?) + + # If requested, confirm that resources were used appropriately (e.g., not left with changes that interfere with + # further reuse) + QA::Resource::ReusableCollection.validate_resource_reuse if QA::Runtime::Env.validate_resource_reuse? + + # If any tests failed, leave the resources behind to help troubleshoot, otherwise remove them. + # Do not remove the shared resource on live environments + begin + next if suite.reporter.failed_examples.present? + next unless QA::Runtime::Scenario.attributes.include?(:gitlab_address) + next if QA::Runtime::Env.running_on_dot_com? + + QA::Resource::ReusableCollection.remove_all_via_api! + rescue QA::Resource::Errors::InternalServerError => e + # Temporarily prevent this error from failing jobs while the cause is investigated + # See https://gitlab.com/gitlab-org/gitlab/-/issues/354387 + QA::Runtime::Logger.debug(e.message) + end + end + + config.append_after(:suite) do + QA::Support::KnapsackReport.move_regenerated_report if QA::Runtime::Env.knapsack? + end + + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + config.disable_monkey_patching! + config.expose_dsl_globally = true + config.profile_examples = 10 + config.order = :random + Kernel.srand config.seed + + # This option allows to use shorthand aliases for adding :focus metadata - fit, fdescribe and fcontext + config.filter_run_when_matching :focus + + if ENV['CI'] && !QA::Runtime::Env.disable_rspec_retry? + # show retry status in spec process + config.verbose_retry = true + + # show exception that triggers a retry if verbose_retry is set to true + config.display_try_failure_messages = true + + non_quarantine_retries = QA::Runtime::Env.ci_project_name =~ /staging|canary|production/ ? 3 : 2 + config.around do |example| + quarantine = example.metadata[:quarantine] + different_quarantine_context = QA::Specs::Helpers::Quarantine.quarantined_different_context?(quarantine) + focused_quarantine = QA::Specs::Helpers::Quarantine.filters.key?(:quarantine) + + # Do not disable retry when spec is quarantined but on different environment + next example.run_with_retry(retry: non_quarantine_retries) if different_quarantine_context && !focused_quarantine + + example.run_with_retry(retry: quarantine ? 1 : non_quarantine_retries) + end + end +end diff --git a/qa/qa/support/formatters/allure_metadata_formatter.rb b/qa/qa/support/formatters/allure_metadata_formatter.rb index 2ed4ee2066a..d1baf87799a 100644 --- a/qa/qa/support/formatters/allure_metadata_formatter.rb +++ b/qa/qa/support/formatters/allure_metadata_formatter.rb @@ -20,11 +20,11 @@ module QA def start(_start_notification) return unless merge_request_iid # on main runs allure native history has pass rate already - save_failures - log(:debug, "Fetched #{failures.length} flaky testcases!") + save_flaky_specs + log(:debug, "Fetched #{flaky_specs.length} flaky testcases!") rescue StandardError => e log(:error, "Failed to fetch flaky spec data for report: #{e}") - @failures = {} + @flaky_specs = {} end # Finished example @@ -53,6 +53,8 @@ module QA return unless issue_link return example.issue('Quarantine issue', issue_link) if issue_link.is_a?(String) return issue_link.each { |link| example.issue('Quarantine issue', link) } if issue_link.is_a?(Array) + rescue StandardError => e + log(:error, "Failed to add quarantine issue linkt for example '#{example.description}', error: #{e}") end # Add failure issues link @@ -65,6 +67,8 @@ module QA 'Failure issues', "https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}" ) + rescue StandardError => e + log(:error, "Failed to add failure issue link for example '#{example.description}', error: #{e}") end # Add ci job link @@ -75,6 +79,8 @@ module QA return unless Runtime::Env.running_in_ci? example.add_link(name: "Job(#{Runtime::Env.ci_job_name})", url: Runtime::Env.ci_job_url) + rescue StandardError => e + log(:error, "Failed to add failure issue link for example '#{example.description}', error: #{e}") end # Mark test as flaky @@ -82,21 +88,22 @@ module QA # @param [RSpec::Core::Example] example # @return [void] def set_flaky_status(example) - return unless merge_request_iid - return unless example.execution_result.status == :failed && failures.key?(example.metadata[:testcase]) + return unless merge_request_iid && flaky_specs.key?(example.metadata[:testcase]) example.set_flaky - example.parameter("pass_rate", "#{failures[example.metadata[:testcase]].round(1)}%") - log(:debug, "Setting spec as flaky due to present failures in last 14 days!") + example.parameter("pass_rate", "#{flaky_specs[example.metadata[:testcase]].round(1)}%") + log(:debug, "Setting spec as flaky because it's pass rate is below 98%") + rescue StandardError => e + log(:error, "Failed to add spec pass rate data for example '#{example.description}', error: #{e}") end - # Failed spec testcases + # Flaky specs with pass rate below 98% # # @return [Array] - def failures - @failures ||= influx_data.lazy.each_with_object({}) do |data, result| - # TODO: replace with mr_iid once stats are populated - records = data.records.reject { |r| r.values["_value"] == env("CI_PIPELINE_ID") } + def flaky_specs + @flaky_specs ||= influx_data.lazy.each_with_object({}) do |data, result| + # Do not consider failures in same merge request + records = data.records.reject { |r| r.values["_value"] == merge_request_iid } runs = records.count failed = records.count { |r| r.values["status"] == "failed" } @@ -107,7 +114,7 @@ module QA end.compact end - alias_method :save_failures, :failures + alias_method :save_flaky_specs, :flaky_specs # Records of previous failures for runs of same type # @@ -122,7 +129,7 @@ module QA |> filter(fn: (r) => r.run_type == "#{run_type}" and r.status != "pending" and r.quarantined == "false" and - r._field == "pipeline_id" + r._field == "merge_request_iid" ) |> group(columns: ["testcase"]) QUERY diff --git a/qa/qa/support/gitlab_address.rb b/qa/qa/support/gitlab_address.rb new file mode 100644 index 00000000000..d978bb2eee5 --- /dev/null +++ b/qa/qa/support/gitlab_address.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module QA + module Support + class GitlabAddress + class << self + # Define gitlab address + # + # @param [String] address + # @return [void] + def define_gitlab_address_attribute!(address = Runtime::Env.gitlab_url) + return if initialized? + + validate_address(address) + + Runtime::Scenario.define(:gitlab_address, address) + # Define the "About" page as an `about` subdomain. + # @example + # Given *gitlab_address* = 'https://gitlab.com/' #=> https://about.gitlab.com/ + # Given *gitlab_address* = 'https://staging.gitlab.com/' #=> https://about.staging.gitlab.com/ + # Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/ + Runtime::Scenario.define(:about_address, URI(address).tap { |uri| uri.host = "about.#{uri.host}" }.to_s) + + @initialized = true + end + + private + + # Gitlab address already set up + # + # @return [Boolean] + def initialized? + @initialized + end + + # Validate if address is a valid url + # + # @param [String] address + # @return [void] + def validate_address(address) + Runtime::Address.valid?(address) || raise( + ::ArgumentError, "Configured gitlab address is not a valid url: #{address}" + ) + end + end + end + end +end diff --git a/qa/qa/tools/knapsack_report.rb b/qa/qa/support/knapsack_report.rb index e50c4fe63d2..998802fe8b7 100644 --- a/qa/qa/tools/knapsack_report.rb +++ b/qa/qa/support/knapsack_report.rb @@ -3,7 +3,7 @@ require "fog/google" module QA - module Tools + module Support class KnapsackReport extend SingleForwardable @@ -20,11 +20,10 @@ module QA # # @return [void] def configure! - ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb" - ENV["KNAPSACK_REPORT_PATH"] = report_path - - Knapsack.logger = QA::Runtime::Logger.logger + return unless QA::Runtime::Env.knapsack? + setup_logger! + setup_environment! download_report end @@ -33,6 +32,8 @@ module QA # @return [void] def download_report logger.debug("Downloading latest knapsack report for '#{report_name}' to '#{report_path}'") + return logger.debug("Report already exists, skipping!") if File.exist?(report_path) + file = client.get_object(BUCKET, report_file) File.write(report_path, file[:body]) rescue StandardError => e @@ -89,11 +90,27 @@ module QA private + # Setup knapsack logger + # + # @return [void] + def setup_logger! + Knapsack.logger = QA::Runtime::Logger.logger + end + + # Set knapsack environment variables + # + # @return [void] + def setup_environment! + ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb" + ENV["KNAPSACK_TEST_DIR"] = "qa/specs" + ENV["KNAPSACK_REPORT_PATH"] = report_path + end + # Logger instance # # @return [Logger] def logger - @logger ||= Logger.new($stdout) + @logger ||= Knapsack.logger end # GCS client @@ -131,7 +148,7 @@ module QA # # @return [String] def report_name - @report_name ||= ENV["CI_JOB_NAME"].split(" ").first.tr(":", "-") + @report_name ||= ENV["QA_KNAPSACK_REPORT_NAME"] || ENV["CI_JOB_NAME"].split(" ").first.tr(":", "-") end # GCS credentials json diff --git a/qa/qa/support/matchers/have_matcher.rb b/qa/qa/support/matchers/have_matcher.rb index b96566a9e5d..734a8890536 100644 --- a/qa/qa/support/matchers/have_matcher.rb +++ b/qa/qa/support/matchers/have_matcher.rb @@ -24,6 +24,7 @@ module QA snippet_description tag label + variable ].each do |predicate| RSpec::Matchers.define "have_#{predicate}" do |*args, **kwargs| match do |page_object| diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index b402639b843..bc5ee645965 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -82,6 +82,12 @@ module QA super end + def click_via_capybara(method, locator) + log("clicking via capybara using '#{method}(#{locator})'") + + super + end + def fill_element(name, content) masked_content = name.to_s.match?(/token|key|password/) ? '*****' : content diff --git a/qa/qa/tools/delete_test_snippets.rb b/qa/qa/tools/delete_test_snippets.rb new file mode 100644 index 00000000000..5da962b14f3 --- /dev/null +++ b/qa/qa/tools/delete_test_snippets.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# This script deletes personal snippets for a specific user +# - Specify `delete_before` to delete only snippets that were created before the given date (default: yesterday) +# - If `dry_run` is true the script will list snippets to be deleted, but it won't delete them +# +# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS +# - GITLAB_QA_ACCESS_TOKEN should have API access and belong to the user whose snippets will be deleted + +module QA + module Tools + class DeleteTestSnippets + include Support::API + + ITEMS_PER_PAGE = '100' + + def initialize(delete_before: (Date.today - 1).to_s, dry_run: false) + raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS'] + raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] + + @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], + personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) + @delete_before = Date.parse(delete_before) + @dry_run = dry_run + end + + def run + $stdout.puts 'Running...' + + response = head Runtime::API::Request.new(@api_client, "/snippets", per_page: ITEMS_PER_PAGE).url + total_pages = response.headers[:x_total_pages] + + test_snippet_ids = fetch_snippet_ids(total_pages) + $stdout.puts "Number of test snippets to be deleted: #{test_snippet_ids.length}" + + return if dry_run? + + delete_snippets(test_snippet_ids) unless test_snippet_ids.empty? + $stdout.puts "\nDone" + end + + private + + attr_reader :dry_run + alias_method :dry_run?, :dry_run + + def delete_snippets(snippet_ids) + $stdout.puts "Deleting #{snippet_ids.length} snippet(s)..." + snippet_ids.each do |snippet_id| + delete_response = delete Runtime::API::Request.new(@api_client, "/snippets/#{snippet_id}").url + dot_or_f = delete_response.code == 204 ? "\e[32m.\e[0m" : "\e[31mF\e[0m" + print dot_or_f + end + end + + def fetch_snippet_ids(pages) + snippet_ids = [] + + pages.to_i.times do |page_no| + get_snippet_response = get Runtime::API::Request.new(@api_client, "/snippets", + page: (page_no + 1).to_s, per_page: ITEMS_PER_PAGE).url + snippets = JSON.parse(get_snippet_response.body).select do |snippet| + to_delete = Date.parse(snippet['created_at']) < @delete_before + + if dry_run? + puts "Snippet title: #{snippet['title']}\tcreated_at: #{snippet['created_at']}\tdelete? #{to_delete}" + end + + to_delete + end + snippet_ids.concat(snippets.map { |snippet| snippet['id'] }) + end + + snippet_ids.uniq + end + end + end +end diff --git a/qa/spec/README.md b/qa/spec/README.md new file mode 100644 index 00000000000..b1fc38fb55d --- /dev/null +++ b/qa/spec/README.md @@ -0,0 +1,7 @@ +# QA framework unit tests + +To run framework unit tests, following command can be used: + +```shell +bundle exec rspec -O .rspec_internal +``` diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb index 6b100f9dc16..a6a49f5907a 100644 --- a/qa/spec/git/repository_spec.rb +++ b/qa/spec/git/repository_spec.rb @@ -206,19 +206,19 @@ RSpec.describe QA::Git::Repository do it_behaves_like 'command with retries' do let(:command) { "git ls-remote #{repo_uri_with_credentials}" } - let(:result_output) { +'packet: git< version 2' } + let(:result_output) { +'packet: ls-remote< version 2' } let(:command_return) { '2' } let(:extra_env_vars) { ["GIT_TRACE_PACKET=1"] } end it "reports the detected version" do - expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: git< version 2")) + expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: ls-remote< version 2")) expect(call_method).to eq('2') end it 'reports unknown if version is unknown' do - expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: git< version -1")) + expect(repository).to receive(:run).and_return(described_class::Result.new(any_args, 0, "packet: ls-remote< version -1")) expect(call_method).to eq('unknown') end diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb index 9800f92b306..56521cc13bc 100644 --- a/qa/spec/scenario/template_spec.rb +++ b/qa/spec/scenario/template_spec.rb @@ -1,35 +1,49 @@ # frozen_string_literal: true RSpec.describe QA::Scenario::Template do - let(:feature) { spy('Runtime::Feature') } - let(:release) { spy('Runtime::Release') } + let(:release) { spy('QA::Runtime::Release') } # rubocop:disable RSpec/VerifiedDoubles + let(:feature) { class_spy('QA::Runtime::Feature') } + let(:scenario) { class_spy('QA::Runtime::Scenario') } + let(:runner) { class_spy('QA::Specs::Runner') } + let(:gitlab_address) { 'https://gitlab.com/' } + let(:gitlab_address_from_env) { 'https://staging.gitlab.com/' } before do stub_const('QA::Runtime::Release', release) stub_const('QA::Runtime::Feature', feature) - allow(QA::Specs::Runner).to receive(:perform) - allow(QA::Runtime::Address).to receive(:valid?).and_return(true) + stub_const('QA::Runtime::Scenario', scenario) + stub_const('QA::Specs::Runner', runner) + + allow(QA::Runtime::Env).to receive(:knapsack?).and_return(false) + allow(QA::Runtime::Env).to receive(:gitlab_url).and_return(gitlab_address_from_env) + + allow(QA::Runtime::Browser).to receive(:configure!) + + allow(scenario).to receive(:attributes).and_return({ gitlab_address: gitlab_address }) + allow(scenario).to receive(:define) + + QA::Support::GitlabAddress.instance_variable_set(:@initialized, false) end it 'allows a feature to be enabled' do subject.perform({ gitlab_address: gitlab_address, enable_feature: 'a-feature' }) expect(feature).to have_received(:enable).with('a-feature') + expect(feature).to have_received(:disable).with('a-feature') end it 'allows a feature to be disabled' do - allow(QA::Runtime::Feature).to receive(:enabled?) - .with('another-feature').and_return(true) + allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(true) subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) expect(feature).to have_received(:disable).with('another-feature') + expect(feature).to have_received(:enable).with('another-feature') end it 'does not disable a feature if already disabled' do - allow(QA::Runtime::Feature).to receive(:enabled?) - .with('another-feature').and_return(false) + allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(false) subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) @@ -39,7 +53,8 @@ RSpec.describe QA::Scenario::Template do it 'ensures an enabled feature is disabled afterwards' do allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test') - expect { subject.perform({ gitlab_address: gitlab_address, enable_feature: 'a-feature' }) }.to raise_error('failed test') + expect { subject.perform({ gitlab_address: gitlab_address, enable_feature: 'a-feature' }) } + .to raise_error('failed test') expect(feature).to have_received(:enable).with('a-feature') expect(feature).to have_received(:disable).with('a-feature') @@ -47,11 +62,10 @@ RSpec.describe QA::Scenario::Template do it 'ensures a disabled feature is enabled afterwards' do allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test') + allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(true) - allow(QA::Runtime::Feature).to receive(:enabled?) - .with('another-feature').and_return(true) - - expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) }.to raise_error('failed test') + expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) } + .to raise_error('failed test') expect(feature).to have_received(:disable).with('another-feature') expect(feature).to have_received(:enable).with('another-feature') @@ -59,25 +73,35 @@ RSpec.describe QA::Scenario::Template do it 'ensures a disabled feature is not enabled afterwards if it was disabled earlier' do allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test') + allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(false) - allow(QA::Runtime::Feature).to receive(:enabled?) - .with('another-feature').and_return(false) - - expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) }.to raise_error('failed test') + expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) } + .to raise_error('failed test') expect(feature).not_to have_received(:disable).with('another-feature') expect(feature).not_to have_received(:enable).with('another-feature') end - it 'defines an about address by default' do - subject.perform( { gitlab_address: gitlab_address }) + it 'defines gitlab address from positional argument' do + allow(scenario).to receive(:attributes).and_return({}) + + subject.perform({}, gitlab_address) + + expect(scenario).to have_received(:define).with(:gitlab_address, gitlab_address) + expect(scenario).to have_received(:define).with(:about_address, 'https://about.gitlab.com/') + end - expect(QA::Runtime::Scenario.gitlab_address).to eq(gitlab_address) - expect(QA::Runtime::Scenario.about_address).to eq('https://about.gitlab.com/') + it "defaults to gitlab address from env" do + allow(scenario).to receive(:attributes).and_return({}) + + subject.perform({}) + + expect(scenario).to have_received(:define).with(:gitlab_address, gitlab_address_from_env) + end - subject.perform({ gitlab_address: 'http://gitlab-abc.test/' }) + it 'defines klass attribute' do + subject.perform({ gitlab_address: gitlab_address }) - expect(QA::Runtime::Scenario.gitlab_address).to eq('http://gitlab-abc.test/') - expect(QA::Runtime::Scenario.about_address).to eq('http://about.gitlab-abc.test/') + expect(scenario).to have_received(:define).with(:klass, 'QA::Scenario::Template') end end diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb index 9532ec35b95..4cdd0c0cb0e 100644 --- a/qa/spec/scenario/test/integration/mattermost_spec.rb +++ b/qa/spec/scenario/test/integration/mattermost_spec.rb @@ -3,24 +3,10 @@ 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] } - let(:args) do - { - gitlab_address: 'http://gitlab_address', - mattermost_address: 'http://mattermost_address' - } - end - + let(:args) { { gitlab_address: 'http://gitlab_address' } } let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] } let(:tags) { [:mattermost] } - let(:options) { ['path1']} - - it 'requires a GitHub access token' do - subject.perform(args) - - expect(attributes).to have_received(:define) - .with(:mattermost_address, 'http://mattermost_address') - end + let(:options) { ['path1'] } end end end diff --git a/qa/spec/specs/scenario_shared_examples.rb b/qa/spec/scenario_shared_examples.rb index 7d806d50d21..944e309c4cb 100644 --- a/qa/spec/specs/scenario_shared_examples.rb +++ b/qa/spec/scenario_shared_examples.rb @@ -2,7 +2,7 @@ module QA RSpec.shared_examples 'a QA scenario class' do - let(:attributes) { class_spy('Runtime::Scenario') } + let(:scenario) { class_spy('Runtime::Scenario') } let(:runner) { class_spy('Specs::Runner') } let(:release) { class_spy('Runtime::Release') } let(:feature) { class_spy('Runtime::Feature') } @@ -15,24 +15,19 @@ module QA before do stub_const('QA::Specs::Runner', runner) stub_const('QA::Runtime::Release', release) - stub_const('QA::Runtime::Scenario', attributes) + stub_const('QA::Runtime::Scenario', scenario) stub_const('QA::Runtime::Feature', feature) - allow(attributes).to receive(:gitlab_address).and_return(args[:gitlab_address]) + allow(QA::Runtime::Browser).to receive(:configure!) + + allow(scenario).to receive(:attributes).and_return(args) allow(runner).to receive(:perform).and_yield(runner) - allow(QA::Runtime::Address).to receive(:valid?).and_return(true) end it 'responds to perform' do expect(subject).to respond_to(:perform) end - it 'sets an address of the subject' do - subject.perform(args) - - expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once) - end - it 'performs before hooks only once' do subject.perform(args) @@ -58,7 +53,7 @@ module QA described_class.launch!(named_options) args do |k, v| - expect(attributes).to have_received(:define).with(k, v) + expect(scenario).to have_received(:define).with(k, v) end end @@ -67,7 +62,7 @@ module QA end it 'passes on options after --' do - expect(described_class).to receive(:perform).with(attributes, *%w[--tag quarantine]) + expect(described_class).to receive(:perform).with(args, *%w[--tag quarantine]) described_class.launch!(named_options.push(*%w[-- --tag quarantine])) end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index b81c41bb79c..95970800a4e 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -2,134 +2,4 @@ require_relative '../qa' -require_relative 'qa_deprecation_toolkit_env' -QaDeprecationToolkitEnv.configure! - -Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack? - -QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run -QA::Runtime::AllureReport.configure! -QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) - -Dir[::File.join(__dir__, "support/shared_examples/*.rb")].sort.each { |f| require f } -Dir[::File.join(__dir__, "support/shared_contexts/*.rb")].sort.each { |f| require f } - -RSpec.configure do |config| - config.include QA::Support::Matchers::EventuallyMatcher - config.include QA::Support::Matchers::HaveMatcher - - config.add_formatter QA::Support::Formatters::ContextFormatter - config.add_formatter QA::Support::Formatters::QuarantineFormatter - config.add_formatter QA::Support::Formatters::FeatureFlagFormatter - config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics? - - config.before(:suite) do |suite| - QA::Resource::ReusableCollection.register_resource_classes do |collection| - QA::Resource::ReusableProject.register(collection) - QA::Resource::ReusableGroup.register(collection) - end - end - - config.prepend_before do |example| - QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}") - QA::Runtime::Example.current = example - - # Reset fabrication counters tracked in resource base - Thread.current[:api_fabrication] = 0 - Thread.current[:browser_ui_fabrication] = 0 - end - - config.after do - # If a .netrc file was created during the test, delete it so that subsequent tests don't try to use the same logins - QA::Git::Repository.new.delete_netrc - end - - # Add fabrication time to spec metadata - config.append_after do |example| - example.metadata[:api_fabrication] = Thread.current[:api_fabrication] - example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication] - end - - config.after(:context) do - if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?) - QA::Page::Main::Menu.perform(&:sign_out) - raise( - <<~ERROR - The test left the browser signed in. - - Usually, Capybara prevents this from happening but some things can - interfere. For example, if it has an `after(:context)` block that logs - in, the browser will stay logged in and this will cause the next test - to fail. - - Please make sure the test does not leave the browser signed in. - ERROR - ) - end - end - - config.after(:suite) do |suite| - # Write all test created resources to JSON file - QA::Tools::TestResourceDataProcessor.write_to_file(suite.reporter.failed_examples.any?) - - # If requested, confirm that resources were used appropriately (e.g., not left with changes that interfere with - # further reuse) - QA::Resource::ReusableCollection.validate_resource_reuse if QA::Runtime::Env.validate_resource_reuse? - - # If any tests failed, leave the resources behind to help troubleshoot, otherwise remove them. - # Do not remove the shared resource on live environments - begin - next if suite.reporter.failed_examples.present? - next unless QA::Runtime::Scenario.attributes.include?(:gitlab_address) - next if QA::Runtime::Env.running_on_dot_com? - - QA::Resource::ReusableCollection.remove_all_via_api! - rescue QA::Resource::Errors::InternalServerError => e - # Temporarily prevent this error from failing jobs while the cause is investigated - # See https://gitlab.com/gitlab-org/gitlab/-/issues/354387 - QA::Runtime::Logger.debug(e.message) - end - end - - config.append_after(:suite) do - QA::Tools::KnapsackReport.move_regenerated_report if QA::Runtime::Env.knapsack? - end - - config.expect_with :rspec do |expectations| - expectations.include_chain_clauses_in_custom_matcher_descriptions = true - end - - config.mock_with :rspec do |mocks| - mocks.verify_partial_doubles = true - end - - config.shared_context_metadata_behavior = :apply_to_host_groups - config.disable_monkey_patching! - config.expose_dsl_globally = true - config.profile_examples = 10 - config.order = :random - Kernel.srand config.seed - - # show retry status in spec process - config.verbose_retry = true - - # show exception that triggers a retry if verbose_retry is set to true - config.display_try_failure_messages = true - - # This option allows to use shorthand aliases for adding :focus metadata - fit, fdescribe and fcontext - config.filter_run_when_matching :focus - - if ENV['CI'] && !QA::Runtime::Env.disable_rspec_retry? - non_quarantine_retries = QA::Runtime::Env.ci_project_name =~ /staging|canary|production/ ? 3 : 2 - config.around do |example| - quarantine = example.metadata[:quarantine] - different_quarantine_context = QA::Specs::Helpers::Quarantine.quarantined_different_context?(quarantine) - focused_quarantine = QA::Specs::Helpers::Quarantine.filters.key?(:quarantine) - - # Do not disable retry when spec is quarantined but on different environment - next example.run_with_retry(retry: non_quarantine_retries) if different_quarantine_context && !focused_quarantine - - example.run_with_retry(retry: quarantine ? 1 : non_quarantine_retries) - end - end -end +require_relative 'scenario_shared_examples' diff --git a/qa/spec/specs/spec_helper.rb b/qa/spec/specs/spec_helper.rb deleted file mode 100644 index e4514c6c64f..00000000000 --- a/qa/spec/specs/spec_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../qa' - -require_relative 'scenario_shared_examples' diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake index ce4a1679bd1..cfc11d0ba24 100644 --- a/qa/tasks/knapsack.rake +++ b/qa/tasks/knapsack.rake @@ -1,20 +1,34 @@ # frozen_string_literal: true -# rubocop:disable Rails/RakeEnvironment +# rubocop:disable Rails/RakeEnvironment namespace :knapsack do + desc "Run tests with knapsack runner" + task :rspec, [:rspec_args] do |_, args| + rspec_args = args[:rspec_args]&.split(' ') || [] + + unless QA::Runtime::Env.knapsack? + QA::Runtime::Logger.info("This environment is not compatible with parallel knapsack execution!") + QA::Runtime::Logger.info("Falling back to standard execution") + + exit RSpec::Core::Runner.run([*rspec_args, "qa/specs/features"]) + end + + exit QA::Specs::KnapsackRunner.run(rspec_args) + end + desc "Download latest knapsack report" task :download do - QA::Tools::KnapsackReport.download + QA::Support::KnapsackReport.download_report end desc "Merge and upload knapsack report" task :upload, [:glob] do |_task, args| - QA::Tools::KnapsackReport.upload_report(args[:glob]) + QA::Support::KnapsackReport.upload_report(args[:glob]) end desc "Report long running spec files" task :notify_long_running_specs do - QA::Tools::LongRunningSpecReporter.execute + QA::Support::LongRunningSpecReporter.execute end end # rubocop:enable Rails/RakeEnvironment diff --git a/qa/tasks/vulnerabilities.rake b/qa/tasks/vulnerabilities.rake new file mode 100644 index 00000000000..79d6b8683e0 --- /dev/null +++ b/qa/tasks/vulnerabilities.rake @@ -0,0 +1,29 @@ +# frozen_string_literal: true +# rubocop:disable Rails/RakeEnvironment + +# How to run this rake task? +# GITLAB_QA_ACCESS_TOKEN=<access_token> GITLAB_URL="<Gitlab address>" bundle exec rake +# vulnerabilities:setup\[<Project_id>,<Vulnerability_count>\] --trace + +namespace :vulnerabilities do + desc "Set up test data for vulnerability report" + task :setup, [:project_id, :vulnerability_count] do |t, args| + QA::Runtime::Browser.configure! + QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) + + if ENV['GITLAB_URL'].nil? + puts 'ERROR: Exiting rake, Gitlab address not specified as GITLAB_URL environment variable' + exit 1 + end + + if ENV['GITLAB_QA_ACCESS_TOKEN'].nil? + puts 'ERROR: Exiting rake, API access token not provided as GITLAB_QA_ACCESS_TOKEN environment variable' + exit 1 + end + + QA::Runtime::Scenario.define(:gitlab_address, ENV['GITLAB_URL']) + vuln = QA::EE::Resource::VulnerabilityReport.new + vuln.create_vuln_report(args[:project_id], args[:vulnerability_count].to_i) + end +end +# rubocop:enable Rails/RakeEnvironment |