diff options
Diffstat (limited to 'qa')
158 files changed, 1985 insertions, 1449 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile index 7f236a25288..71fc615ac13 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,9 +1,9 @@ ARG DOCKER_VERSION=20.10.14 ARG CHROME_VERSION=106 -ARG QA_BUILD_TARGET=qa +ARG QA_BUILD_TARGET=ee ARG RUBY_VERSION=2.7 -FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS qa +FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS foss LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" ENV DEBIAN_FRONTEND="noninteractive" @@ -39,10 +39,6 @@ RUN bundle config set --local without development \ && bundle install --retry=3 COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/ -# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in FOSS -# The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS) -COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/ -COPY VERSION ./ee/config/feature_flag[s] /home/gitlab/ee/config/feature_flags/ COPY ./config/feature_flags /home/gitlab/config/feature_flags COPY ./config/bundler_setup.rb /home/gitlab/config/ COPY ./lib/gitlab_edition.rb /home/gitlab/lib/ @@ -53,8 +49,15 @@ COPY ./qa /home/gitlab/qa ENTRYPOINT ["bin/test"] -# Add JH files when pass the parameter: `--build-arg QA_BUILD_TARGET=jhqa` -FROM qa AS jhqa +# Add ee files when passing the parameter: `--build-arg QA_BUILD_TARGET=ee` +FROM foss as ee +# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in FOSS +# The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS) +ONBUILD COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/ +ONBUILD COPY VERSION ./ee/config/feature_flag[s] /home/gitlab/ee/config/feature_flags/ + +# Add JH files when passing the parameter: `--build-arg QA_BUILD_TARGET=jhqa` +FROM ee AS jhqa ONBUILD COPY ./jh/qa /home/gitlab/jh/qa ONBUILD COPY ./jh/lib /home/gitlab/jh/lib ONBUILD COPY ./jh/config/feature_flags /home/gitlab/jh/config/feature_flags diff --git a/qa/Gemfile b/qa/Gemfile index b84a22883d1..224fced35dc 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,14 +2,14 @@ source 'https://rubygems.org' -gem 'gitlab-qa', '~> 8', '>= 8.11.0', require: 'gitlab/qa' +gem 'gitlab-qa', '~> 8', '>= 8.14.0', require: 'gitlab/qa' gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile -gem 'allure-rspec', '~> 2.19.0' +gem 'allure-rspec', '~> 2.20.0' gem 'capybara', '~> 3.38.0' gem 'capybara-screenshot', '~> 1.0.26' gem 'rake', '~> 13', '>= 13.0.6' gem 'rspec', '~> 3.12' -gem 'selenium-webdriver', '~> 4.6', '>= 4.6.1' +gem 'selenium-webdriver', '~> 4.7', '>= 4.7.1' gem 'airborne', '~> 0.3.7', require: false # airborne is messing with rspec sandboxed mode so not requiring by default gem 'rest-client', '~> 2.1.0' gem 'rspec-retry', '~> 0.6.2', require: 'rspec/retry' @@ -17,14 +17,14 @@ gem 'rspec_junit_formatter', '~> 0.6.0' gem 'faker', '~> 3.0' gem 'knapsack', '~> 4.0' gem 'parallel_tests', '~> 4.0' -gem 'rotp', '~> 6.2.1' +gem 'rotp', '~> 6.2.2' gem 'parallel', '~> 1.22', '>= 1.22.1' gem 'rainbow', '~> 3.1.1' gem 'rspec-parameterized', '~> 0.5.2' -gem 'octokit', '~> 6.0.0' +gem 'octokit', '~> 6.0.1' gem "faraday-retry", "~> 2.0" gem 'webdrivers', '~> 5.2' -gem 'zeitwerk', '~> 2.4' +gem 'zeitwerk', '~> 2.6', '>= 2.6.6' gem 'influxdb-client', '~> 2.8' gem 'terminal-table', '~> 3.0.2', require: false gem 'slack-notifier', '~> 2.4', require: false @@ -32,15 +32,15 @@ gem 'fog-google', '~> 1.19', require: false gem 'fog-core', '2.1.0', require: false # fog-google generates a ton of warnings with latest core gem "warning", "~> 1.3" -gem 'confiner', '~> 0.3' +gem 'confiner', '~> 0.4' gem 'chemlab', '~> 0.10' gem 'chemlab-library-www-gitlab-com', '~> 0.1', '>= 0.1.1' # dependencies for jenkins client -gem 'nokogiri', '~> 1.13', '>= 1.13.9' +gem 'nokogiri', '~> 1.13', '>= 1.13.10' -gem 'deprecation_toolkit', '~> 2.0.0', require: false +gem 'deprecation_toolkit', '~> 2.0.1', require: false group :development do gem 'pry-byebug', '~> 3.10.1', platform: :mri diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 3ccc5fe9be3..243389e6e4a 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -15,13 +15,14 @@ GEM rack-test (>= 1.1.0, < 2.0) rest-client (>= 2.0.2, < 3.0) rspec (~> 3.8) - allure-rspec (2.19.0) - allure-ruby-commons (= 2.19.0) + allure-rspec (2.20.0) + allure-ruby-commons (= 2.20.0) rspec-core (>= 3.8, < 4) - allure-ruby-commons (2.19.0) + allure-ruby-commons (2.20.0) mime-types (>= 3.3, < 4) oj (>= 3.10, < 4) require_all (>= 2, < 4) + rspec-expectations (~> 3.12) uuid (>= 2.3, < 3) ast (2.4.2) binding_ninja (0.2.3) @@ -47,15 +48,14 @@ GEM watir (>= 6, < 8) chemlab-library-www-gitlab-com (0.1.1) chemlab (~> 0.4) - childprocess (4.1.0) coderay (1.1.2) colorize (0.8.1) concurrent-ruby (1.1.10) - confiner (0.3.0) + confiner (0.4.0) gitlab (>= 4.17) - zeitwerk (~> 2.5.1) + zeitwerk (>= 2.5, < 3) declarative (0.0.20) - deprecation_toolkit (2.0.0) + deprecation_toolkit (2.0.1) activesupport (>= 5.2) diff-lcs (1.3) domain_name (0.5.20190701) @@ -100,7 +100,7 @@ GEM gitlab (4.18.0) httparty (~> 0.18) terminal-table (>= 1.5.1) - gitlab-qa (8.11.0) + gitlab-qa (8.14.0) activesupport (~> 6.1) gitlab (~> 4.18.0) http (~> 5.0) @@ -155,7 +155,7 @@ GEM httpclient (2.8.3) i18n (1.12.0) concurrent-ruby (~> 1.0) - influxdb-client (2.8.0) + influxdb-client (2.9.0) jwt (2.5.0) knapsack (4.0.0) rake @@ -178,10 +178,10 @@ GEM multi_json (1.15.0) multi_xml (0.6.0) netrc (0.11.0) - nokogiri (1.13.9) + nokogiri (1.13.10) mini_portile2 (~> 2.8.0) racc (~> 1.4) - octokit (6.0.0) + octokit (6.0.1) faraday (>= 1, < 3) sawyer (~> 0.9) oj (3.13.23) @@ -202,7 +202,7 @@ GEM byebug (~> 11.0) pry (>= 0.13, < 0.15) public_suffix (5.0.0) - racc (1.6.0) + racc (1.6.1) rack (2.2.3.1) rack-test (1.1.0) rack (>= 1.0, < 3) @@ -221,7 +221,7 @@ GEM netrc (~> 0.8) retriable (3.1.2) rexml (3.2.5) - rotp (6.2.1) + rotp (6.2.2) rspec (3.12.0) rspec-core (~> 3.12.0) rspec-expectations (~> 3.12.0) @@ -252,8 +252,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - selenium-webdriver (4.6.1) - childprocess (>= 0.5, < 5.0) + selenium-webdriver (4.7.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -292,7 +291,7 @@ GEM websocket (1.2.9) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.5.4) + zeitwerk (2.6.6) PLATFORMS ruby @@ -300,40 +299,40 @@ PLATFORMS DEPENDENCIES activesupport (~> 6.1.4.7) airborne (~> 0.3.7) - allure-rspec (~> 2.19.0) + allure-rspec (~> 2.20.0) capybara (~> 3.38.0) capybara-screenshot (~> 1.0.26) chemlab (~> 0.10) chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1) - confiner (~> 0.3) - deprecation_toolkit (~> 2.0.0) + confiner (~> 0.4) + deprecation_toolkit (~> 2.0.1) faker (~> 3.0) faraday-retry (~> 2.0) fog-core (= 2.1.0) fog-google (~> 1.19) - gitlab-qa (~> 8, >= 8.11.0) + gitlab-qa (~> 8, >= 8.14.0) influxdb-client (~> 2.8) knapsack (~> 4.0) - nokogiri (~> 1.13, >= 1.13.9) - octokit (~> 6.0.0) + nokogiri (~> 1.13, >= 1.13.10) + octokit (~> 6.0.1) parallel (~> 1.22, >= 1.22.1) parallel_tests (~> 4.0) pry-byebug (~> 3.10.1) rainbow (~> 3.1.1) rake (~> 13, >= 13.0.6) rest-client (~> 2.1.0) - rotp (~> 6.2.1) + rotp (~> 6.2.2) rspec (~> 3.12) rspec-parameterized (~> 0.5.2) rspec-retry (~> 0.6.2) rspec_junit_formatter (~> 0.6.0) ruby-debug-ide (~> 0.7.3) - selenium-webdriver (~> 4.6, >= 4.6.1) + selenium-webdriver (~> 4.7, >= 4.7.1) slack-notifier (~> 2.4) terminal-table (~> 3.0.2) warning (~> 1.3) webdrivers (~> 5.2) - zeitwerk (~> 2.4) + zeitwerk (~> 2.6, >= 2.6.6) BUNDLED WITH - 2.3.25 + 2.3.26 diff --git a/qa/README.md b/qa/README.md index 564beb4c6e8..4e2d688aa54 100644 --- a/qa/README.md +++ b/qa/README.md @@ -88,6 +88,43 @@ bundle exec bin/qa Test::Instance::All {GDK IP ADDRESS} - Note: If you want to run tests requiring SSH against GDK, you will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md). - Note: If this is your first time running GDK, you can use the password pre-set for `root`. [See supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables). If you have changed your `root` password, use that when exporting `GITLAB_INITIAL_ROOT_PASSWORD`. +#### Run the end-to-end tests on GitLab in Docker + +1. [GitLab can be installed in Docker](https://docs.gitlab.com/ee/install/docker.html). You can use the following command to start an instance that you can visit at `http://127.0.0.1`: + + ``` + docker run \ + --hostname 127.0.0.1 \ + --publish 80:80 --publish 22:22 \ + --name gitlab \ + --shm-size 256m \ + --env GITLAB_OMNIBUS_CONFIG="gitlab_rails['initial_root_password']='5iveL\!fe';" \ + gitlab/gitlab-ee:nightly + ``` + + Notes: + - If you are on a Mac with [Apple Silicon](https://support.apple.com/en-us/HT211814), you will also need to add: `--platform=linux/amd64` + - If you are on Windows, please be aware that [Docker Desktop must be set to use Linux containers](https://learn.microsoft.com/en-us/virtualization/windowscontainers/quick-start/quick-start-windows-10-linux#run-your-first-linux-container). + + +2. Navigate to the QA folder and run the following commands. + + ```bash + cd gitlab/qa + bundle install + export WEBDRIVER_HEADLESS=false + export GITLAB_INITIAL_ROOT_PASSWORD=5iveL\!fe + export QA_GITLAB_URL="http://127.0.0.1" + ``` + +3. Most tests that do not require special setup could then be run with the following command. + + ```bash + bundle exec rspec <path/to/spec.rb> + ``` + +- Note: See the section above for situations that might require adjustment to the commands or to the configuration of the GitLab instance. [You can find more information in the documentation](https://docs.gitlab.com/ee/install/docker.html). + #### Running EE tests When running EE tests you'll need to have a license available. GitLab engineers can [request a license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee). @@ -241,9 +278,8 @@ feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)) if no run all the tests in the `Test::Instance::All` scenario, and then enable the feature flag again if it was enabled earlier. -Note: the QA framework doesn't currently allow you to easily toggle a feature -flag during a single test, [as you can in unit tests](https://docs.gitlab.com/ee/development/feature_flags/index.html), -but [that capability is planned](https://gitlab.com/gitlab-org/quality/team-tasks/issues/77). +Note: You can also [toggle feature +flags in the tests themselves](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/feature_flags.html). Note also that the `--` separator isn't used because `--enable-feature` and `--disable-feature` are QA framework options, not `rspec` options. diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb index 8540bce3da8..04aa1b779ce 100644 --- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb +++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb @@ -35,21 +35,21 @@ module Gitlab div :purchased_usage_total_free # Different UI for free namespace span :purchased_usage_total div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ - h2 :storage_available_alert, text: /purchased storage is available/ + div :additional_storage_alert, text: /purchase additional storage/ def plan_ci_limits - plan_ci_minutes_element.span.text[%r{([^/ ]+)$}] + plan_ci_minutes[/(\d+){2}/] end def additional_ci_limits - additional_ci_minutes_element.span.text[%r{([^/ ]+)$}] + additional_ci_minutes[/(\d+){2}/] end # Waits and Checks if storage available alert presents on the page # # @return [Boolean] True if the alert presents, false if not after 5 second wait - def purchased_storage_available? - storage_available_alert_element.wait_until(timeout: 5, &:present?) + def additional_storage_available? + additional_storage_alert_element.wait_until(timeout: 5, &:present?) rescue Watir::Wait::TimeoutError false end @@ -67,7 +67,7 @@ module Gitlab # # @return [Float] Total purchased storage value in GiB def total_purchased_storage(free_name_space = true) - storage_available_alert_element.wait_until(&:present?) + additional_storage_alert_element.wait_until(&:present?) if free_name_space purchased_usage_total_free.split('/').last.match(/\d+\.\d+/)[0].to_f @@ -13,9 +13,10 @@ Bundler.require(:default) require 'securerandom' require 'pathname' +require 'rainbow/refinement' require 'active_support/core_ext/hash' require 'active_support/core_ext/object/blank' -require 'rainbow/refinement' +require 'active_support/core_ext/module/delegation' module QA root = "#{__dir__}/qa" diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 981b60d1920..8143595a18b 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -8,12 +8,12 @@ module QA def perform_before_hooks if QA::Runtime::Env.admin_personal_access_token.present? QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username, - QA::Runtime::Env.admin_personal_access_token) + QA::Runtime::Env.admin_personal_access_token) end if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present? QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username, - QA::Runtime::Env.personal_access_token) + QA::Runtime::Env.personal_access_token) end # The login page could take some time to load the first time it is visited. @@ -22,6 +22,9 @@ module QA QA::Support::Retrier.retry_on_exception do QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) end + return unless QA::Runtime::Env.allow_local_requests? + + Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true) end end end diff --git a/qa/qa/fixtures/kubernetes_agent/agentk-manifest.yaml.erb b/qa/qa/fixtures/kubernetes_agent/agentk-manifest.yaml.erb index 8eac8419022..e6ec4528d0d 100644 --- a/qa/qa/fixtures/kubernetes_agent/agentk-manifest.yaml.erb +++ b/qa/qa/fixtures/kubernetes_agent/agentk-manifest.yaml.erb @@ -25,9 +25,26 @@ spec: - --token-file=/config/token - --kas-address - "<%= kas_wss_address %>" + <% if QA::Runtime::Env.qa_cookies.to_s.include?("gitlab_canary=true") %> + - --kas-header + - "Cookie: gitlab_canary=true" + <% end %> volumeMounts: - name: token-volume mountPath: /config + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SERVICE_ACCOUNT_NAME + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName volumes: - name: token-volume secret: diff --git a/qa/qa/fixtures/package_managers/maven/group/settings_with_pat.xml.erb b/qa/qa/fixtures/package_managers/maven/group/settings_with_pat.xml.erb index 611c232819f..a2d3e7493ef 100644 --- a/qa/qa/fixtures/package_managers/maven/group/settings_with_pat.xml.erb +++ b/qa/qa/fixtures/package_managers/maven/group/settings_with_pat.xml.erb @@ -7,7 +7,7 @@ <httpHeaders> <property> <name>Private-Token</name> - <value><%= personal_access_token %></value> + <value>${PERSONAL_ACCESS_TOKEN}</value> </property> </httpHeaders> </configuration> diff --git a/qa/qa/flow/alert_settings.rb b/qa/qa/flow/alert_settings.rb new file mode 100644 index 00000000000..0e884f58773 --- /dev/null +++ b/qa/qa/flow/alert_settings.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module QA + module Flow + module AlertSettings + extend self + + def setup_http_endpoint_and_send_alert(integration_name: nil, payload: nil) + integration_name ||= random_word + payload ||= { title: random_word, description: random_word } + Page::Project::Menu.perform(&:go_to_monitor_settings) + Page::Project::Settings::Monitor.perform do |setting| + setting.expand_alerts do |alert| + alert.add_new_integration + alert.select_http_endpoint + alert.enter_integration_name(integration_name) + alert.activate_integration + alert.save_and_create_alert + alert.fill_in_test_payload(payload.to_json) + alert.send_test_alert + end + end + end + + private + + def random_word + Faker::Lorem.word + end + end + end +end diff --git a/qa/qa/flow/login.rb b/qa/qa/flow/login.rb index ec205e0aa86..564a51ee483 100644 --- a/qa/qa/flow/login.rb +++ b/qa/qa/flow/login.rb @@ -14,10 +14,8 @@ module QA result end - def while_signed_in_as_admin(address: :gitlab) - while_signed_in(address: address, admin: true) do - yield - end + def while_signed_in_as_admin(address: :gitlab, &block) + while_signed_in(address: address, admin: true, &block) end def sign_in(as: nil, address: :gitlab, skip_page_validation: false, admin: false) diff --git a/qa/qa/git/location.rb b/qa/qa/git/location.rb index c3733572e70..9ac97a66e53 100644 --- a/qa/qa/git/location.rb +++ b/qa/qa/git/location.rb @@ -1,16 +1,13 @@ # frozen_string_literal: true require 'uri' -require 'forwardable' module QA module Git class Location - extend Forwardable - attr_reader :git_uri, :uri - def_delegators :@uri, :user, :host, :path + delegate :user, :host, :path, to: :@uri # See: config/initializers/1_settings.rb # Settings#build_gitlab_shell_ssh_path_prefix diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb index 3164676f8e4..42dd1083bbe 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/admin/menu.rb @@ -92,16 +92,12 @@ module QA end end - def within_sidebar - within_element(:admin_sidebar_content) do - yield - end + def within_sidebar(&block) + within_element(:admin_sidebar_content, &block) end - def within_submenu(element) - within_element(element) do - yield - end + def within_submenu(element, &block) + within_element(element, &block) end end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index f59b06b4e75..ab83da7dacf 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -13,7 +13,6 @@ module QA include Support::WaitForRequests extend Validatable - extend SingleForwardable ElementNotFound = Class.new(RuntimeError) @@ -31,8 +30,6 @@ module QA end end - def_delegators :evaluator, :view, :views - def initialize @retry_later_backoff = QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME end @@ -276,7 +273,7 @@ module QA visible = kwargs.delete(:visible) visible = visible.nil? && true - try_find_element = lambda do |wait| + try_find_element = ->(wait) do if disabled.nil? has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass, visible: visible) else @@ -422,26 +419,30 @@ module QA URI(page.current_url).host end - def self.path - raise NotImplementedError - end + class << self + def path + raise NotImplementedError + end - def self.evaluator - @evaluator ||= Page::Base::DSL.new - end + def evaluator + @evaluator ||= Page::Base::DSL.new + end - def self.errors - return ["Page class does not have views / elements defined!"] if views.empty? + def errors + return ["Page class does not have views / elements defined!"] if views.empty? - views.flat_map(&:errors) - end + views.flat_map(&:errors) + end - def self.elements - views.flat_map(&:elements) - end + def elements + views.flat_map(&:elements) + end + + def required_elements + elements.select(&:required?) + end - def self.required_elements - elements.select(&:required?) + delegate :view, :views, to: :evaluator end def send_keys_to_element(name, keys) diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb index b6001cf39b5..a57ef38f768 100644 --- a/qa/qa/page/component/blob_content.rb +++ b/qa/qa/page/component/blob_content.rb @@ -72,11 +72,11 @@ module QA private - def within_file_by_number(element, file_number) + def within_file_by_number(element, file_number, &block) if file_number - within_element_by_index(element, file_number - 1) { yield } + within_element_by_index(element, file_number - 1, &block) else - within_element(element) { yield } + within_element(element, &block) end end end diff --git a/qa/qa/page/component/custom_metric.rb b/qa/qa/page/component/custom_metric.rb deleted file mode 100644 index 094979f5e18..00000000000 --- a/qa/qa/page/component/custom_metric.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Component - module CustomMetric - extend QA::Page::PageConcern - - def self.included(base) - super - - base.view 'app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue' do - element :custom_metric_prometheus_title_field - element :custom_metric_prometheus_query_field - element :custom_metric_prometheus_y_label_field - element :custom_metric_prometheus_unit_label_field - element :custom_metric_prometheus_legend_label_field - end - end - - def add_custom_metric - fill_element :custom_metric_prometheus_title_field, 'HTTP Requests Total' - fill_element :custom_metric_prometheus_query_field, 'rate(http_requests_total[5m])' - fill_element :custom_metric_prometheus_y_label_field, 'Requests/second' - fill_element :custom_metric_prometheus_unit_label_field, 'req/sec' - fill_element :custom_metric_prometheus_legend_label_field, 'HTTP requests' - - save_changes - end - - def save_changes - click_button(class: 'btn-success') - end - - def delete_custom_metric - click_button(class: 'btn-danger') - within('.modal-content') { click_button(class: 'btn-danger') } - end - - def edit_custom_metric - fill_element :custom_metric_prometheus_title_field, '' - fill_element :custom_metric_prometheus_title_field, 'Throughput' - - save_changes - end - end - end - end -end diff --git a/qa/qa/page/component/dropdown.rb b/qa/qa/page/component/dropdown.rb new file mode 100644 index 00000000000..e6204fb5332 --- /dev/null +++ b/qa/qa/page/component/dropdown.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Dropdown + include Select2 + + def select_item(item_text) + return super if use_select2? + + find('li.gl-dropdown-item', text: item_text, match: :prefer_exact).click + end + + def has_item?(item_text) + return super if use_select2? + + has_css?('li.gl-dropdown-item', text: item_text, match: :prefer_exact) + end + + def current_selection + return super if use_select2? + + expand_select_list unless dropdown_open? + find('span.gl-dropdown-button-text').text + end + + def clear_current_selection_if_present + return super if use_select2? + + expand_select_list unless dropdown_open? + + if has_css?('button[data-testid="listbox-reset-button"]') + find('button[data-testid="listbox-reset-button"]').click + elsif dropdown_open? + expand_select_list + end + end + + def search_item(item_text) + return super if use_select2? + + find('div.gl-search-box-by-type input[type="Search"]').set(item_text) + wait_for_search_to_complete + end + + def search_and_select(item_text) + return super if use_select2? + + QA::Runtime::Logger.info "Searching and selecting: #{item_text}" + + search_item(item_text) + + unless has_item?(item_text) + raise QA::Page::Base::ElementNotFound, %(Couldn't find option named "#{item_text}") + end + + select_item(item_text) + end + + def search_and_select_exact(item_text) + return super if use_select2? + + QA::Runtime::Logger.info "Searching and selecting: #{item_text}" + + search_item(item_text) + + unless has_item?(item_text) + raise QA::Page::Base::ElementNotFound, %(Couldn't find option named "#{item_text}") + end + + find('li.gl-dropdown-item span:nth-child(2)', text: item_text, exact_text: true).click + end + + def expand_select_list + return super if use_select2? + + find('svg.dropdown-chevron').click + end + + def wait_for_search_to_complete + return super if use_select2? + + Support::WaitForRequests.wait_for_requests + + has_css?('div[data-testid="listbox-search-loader"]', wait: 1) + has_no_css?('div[data-testid="listbox-search-loader"]') + end + + def dropdown_open? + return super if use_select2? + + has_css?('ul.gl-dropdown-contents', wait: 1) + end + + def find_input_by_prefix_and_set(element_prefix, item_text) + find("input[id^=\"#{element_prefix}\"]").set(item_text) + end + + private + + # rubocop:disable Gitlab/PredicateMemoization + def use_select2? + @use_select2 ||= has_css?('.select2-container', wait: 1) + end + # rubocop:enable Gitlab/PredicateMemoization + end + end + end +end diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb index 27dce152367..295b5134bd5 100644 --- a/qa/qa/page/component/invite_members_modal.rb +++ b/qa/qa/page/component/invite_members_modal.rb @@ -76,10 +76,7 @@ module QA def set_access_level(access_level) # Guest option is selected by default, skipping these steps if desired option is 'Guest' - unless access_level == 'Guest' - click_element :access_level_dropdown - click_button access_level - end + select_element(:access_level_dropdown, access_level) unless access_level == 'Guest' end def send_invite diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb index fb2e7478684..0a31dee2b4f 100644 --- a/qa/qa/page/component/issuable/sidebar.rb +++ b/qa/qa/page/component/issuable/sidebar.rb @@ -22,19 +22,19 @@ module QA element :reviewers_edit_button end - base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue' do + base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/labels_select_root.vue' do element :labels_block end - base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue' do + base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view.vue' do element :dropdown_input_field end - base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue' do + base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_contents.vue' do element :labels_dropdown_content end - base.view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue' do + base.view 'app/assets/javascripts/sidebar/components/labels/labels_select_widget/dropdown_value.vue' do element :selected_label_content end diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index 47ed1a9616b..4e1c7f3e2bb 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -170,7 +170,7 @@ module QA # wait for the page to reload after deletion wait_until(reload: false) do has_no_element?(:delete_snippet_button) && - has_no_element?(:snippet_action_button, action: 'Delete') + has_no_element?(:snippet_action_button, action: 'Delete') end end diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb index b9b676ee3c4..e66019279ce 100644 --- a/qa/qa/page/file/edit.rb +++ b/qa/qa/page/file/edit.rb @@ -8,8 +8,8 @@ module QA include Shared::CommitButton include Shared::Editor - view 'app/assets/javascripts/editor/components/source_editor_toolbar_button.vue' do - element :editor_toolbar_button + view 'app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js' do + element :editor_toolbar_button, "qaSelector: 'editor_toolbar_button'" # rubocop:disable QA/ElementWithPattern end def has_markdown_preview?(component, content) diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb index 86585eee121..bb5a3485531 100644 --- a/qa/qa/page/group/settings/general.rb +++ b/qa/qa/page/group/settings/general.rb @@ -40,6 +40,14 @@ module QA element :project_creation_level_dropdown end + view 'app/views/groups/settings/_transfer.html.haml' do + element :transfer_group_content + end + + view 'app/assets/javascripts/groups/components/transfer_group_form.vue' do + element :transfer_group_button + end + def set_group_name(name) find_element(:group_name_field).send_keys([:command, 'a'], :backspace) find_element(:group_name_field).set name @@ -102,6 +110,38 @@ module QA click_element(:save_permissions_changes_button) end + + def transfer_group(source_group, target_group) + QA::Runtime::Logger.info "Transferring group: #{source_group.path} to target group: #{target_group.path}" + + expand_content(:advanced_settings_content) + + scroll_to_transfer_group_content + + select_namespace(target_group.path) + + wait_for_enabled_transfer_group_button + click_element(:transfer_group_button) + + fill_confirmation_text(source_group.path) + confirm_transfer + end + + private + + def scroll_to_transfer_group_content + retry_until(sleep_interval: 1, message: 'Waiting for transfer group content to display') do + has_element?(:transfer_group_content, wait: 3) + end + + scroll_to_element :transfer_group_content + end + + def wait_for_enabled_transfer_group_button + retry_until(sleep_interval: 1, message: 'Waiting for transfer group button to be enabled') do + has_element?(:transfer_group_button, disabled: false, wait: 3) + end + end end end end diff --git a/qa/qa/page/group/settings/group_deploy_tokens.rb b/qa/qa/page/group/settings/group_deploy_tokens.rb index 7d908f473de..c1c3303113b 100644 --- a/qa/qa/page/group/settings/group_deploy_tokens.rb +++ b/qa/qa/page/group/settings/group_deploy_tokens.rb @@ -54,12 +54,10 @@ module QA private - def within_new_project_deploy_token + def within_new_project_deploy_token(&block) has_element?(:created_deploy_token_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) - within_element(:created_deploy_token_container) do - yield - end + within_element(:created_deploy_token_container, &block) end end end diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index a30d489e6ff..46ab1e35510 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -39,7 +39,7 @@ module QA end def group_id - find_element(:group_id_content).text.delete('Group ID: ') + find_element(:group_id_content).text.delete('Group ID: ').sub(/\n.*/, '') end def leave_group diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index d7ca8223862..8af78bb86c6 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -12,6 +12,10 @@ module QA element :change_password_button end + view 'app/views/devise/sessions/new.html.haml' do + element :register_link + end + view 'app/views/devise/sessions/_new_base.html.haml' do element :login_field element :password_field diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index ecd71e7c2f4..1e050d79e23 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -9,6 +9,7 @@ module QA view 'app/views/layouts/header/_current_user_dropdown.html.haml' do element :sign_out_link element :edit_profile_link + element :user_profile_link end view 'app/views/layouts/header/_default.html.haml' do @@ -39,6 +40,7 @@ module QA element :projects_dropdown element :groups_dropdown element :snippets_link + element :menu_item_link end view 'app/views/layouts/_search.html.haml' do diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index 909b37943ff..dc2f908a906 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -13,7 +13,7 @@ module QA element :source_branch_dropdown end - view 'app/views/projects/merge_requests/show.html.haml' do + view 'app/views/projects/merge_requests/_page.html.haml' do element :diffs_tab end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index e1add9ad434..aacff7c4172 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -126,7 +126,7 @@ module QA element :title_content, required: true end - view 'app/views/projects/merge_requests/show.html.haml' do + view 'app/views/projects/merge_requests/_page.html.haml' do element :notes_tab, required: true element :commits_tab, required: true element :diffs_tab, required: true @@ -366,7 +366,7 @@ module QA # Revisit after merge page re-architect is done https://gitlab.com/gitlab-org/gitlab/-/issues/300042 # To remove page refresh logic if possible wait_until_ready_to_merge - wait_until { !find_element(:merge_button).text.include?('when pipeline succeeds') } + wait_until { !find_element(:merge_button).text.include?('when pipeline succeeds') } # rubocop:disable Rails/NegateInclude click_element(:merge_button) end @@ -390,6 +390,7 @@ module QA def click_open_in_web_ide click_element(:mr_code_dropdown) click_element(:open_in_web_ide_button) + page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) wait_for_requests end @@ -433,7 +434,11 @@ module QA end def revert_change! - click_element(:revert_button, Page::Component::CommitModal) + # retry when the modal doesn't appear for large MRs as the onClick listener is initialized after the click + # https://gitlab.com/gitlab-org/gitlab/-/issues/366336 + retry_on_exception do + click_element(:revert_button, Page::Component::CommitModal) + end click_element(:submit_commit_button) end diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb index 16aa60262d8..2add02b5c48 100644 --- a/qa/qa/page/profile/two_factor_auth.rb +++ b/qa/qa/page/profile/two_factor_auth.rb @@ -25,7 +25,8 @@ module QA def click_configure_it_later_button # TO DO: Investigate why button does not appear sometimes: # https://gitlab.com/gitlab-org/gitlab/-/issues/382698 - return unless has_element?(:configure_it_later_button) + page.refresh + return unless has_element?(:configure_it_later_button, wait: 60) click_element :configure_it_later_button wait_until(max_duration: 10, message: "Waiting for create a group page") do diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index 75468c74814..c48b1a67d90 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -81,7 +81,19 @@ module QA reload: false, skip_finished_loading_check_on_refresh: true ) do - has_element?(:import_status_indicator, text: "Complete") + status_selector = 'import_status_indicator' + is_partial_import = has_css?(status_selector, text: "Partial import") + + # Temporarily adding this for investigation purposes. This makes sure that the details section is + # expanded when the screenshot is taken when the test fails. This can be removed or repurposed later + # after investigation. Related: https://gitlab.com/gitlab-org/gitlab/-/issues/385252#note_1211218434 + if is_partial_import + within_element_by_index(:import_status_indicator, 0) do + find('button').click + end + end + + has_element?(status_selector, text: "Complete") end end end diff --git a/qa/qa/page/project/infrastructure/kubernetes/show.rb b/qa/qa/page/project/infrastructure/kubernetes/show.rb index 6de5024e525..8725f64fe32 100644 --- a/qa/qa/page/project/infrastructure/kubernetes/show.rb +++ b/qa/qa/page/project/infrastructure/kubernetes/show.rb @@ -9,6 +9,9 @@ module QA view 'app/assets/javascripts/clusters/forms/components/integration_form.vue' do element :integration_status_toggle element :base_domain_field + end + + view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do element :save_changes_button end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index b1417d9b9db..2f8ffc634ac 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -67,10 +67,6 @@ module QA click_element :close_issue_button end - def has_metrics_unfurled? - has_element?(:prometheus_graph_widgets, wait: 30) - end - def has_reopen_issue_button? has_element?(:reopen_issue_button) end diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 5506c5ed4d9..24fd34b4d22 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -15,7 +15,7 @@ module QA element :pipeline_path, required: true end - view 'app/assets/javascripts/jobs/components/job/sidebar/legacy_sidebar_header.vue' do + view 'app/assets/javascripts/jobs/components/job/sidebar/sidebar_header.vue' do element :retry_button end diff --git a/qa/qa/page/project/monitor/alerts/index.rb b/qa/qa/page/project/monitor/alerts/index.rb new file mode 100644 index 00000000000..50b69d59db7 --- /dev/null +++ b/qa/qa/page/project/monitor/alerts/index.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Monitor + module Alerts + class Index < Page::Base + view 'app/assets/javascripts/alert_management/components/alert_management_table.vue' do + element :alert_table_container, required: true + end + + def has_alert_with_title?(title) + has_link?(title) + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/monitor/metrics/show.rb b/qa/qa/page/project/monitor/metrics/show.rb deleted file mode 100644 index 59602d0fcf7..00000000000 --- a/qa/qa/page/project/monitor/metrics/show.rb +++ /dev/null @@ -1,134 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Monitor - module Metrics - class Show < Page::Base - EXPECTED_TITLE = 'Memory Usage (Total)' - LOADING_MESSAGE = 'Waiting for performance data' - - view 'app/assets/javascripts/monitoring/components/dashboard.vue' do - element :prometheus_graphs_content - end - - view 'app/assets/javascripts/monitoring/components/dashboard_header.vue' do - element :dashboards_filter_dropdown - element :environments_dropdown - element :range_picker_dropdown - end - - view 'app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue' do - element :actions_menu_dropdown - element :edit_dashboard_button_enabled - end - - view 'app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue' do - element :duplicate_dashboard_filename_field - end - - view 'app/assets/javascripts/monitoring/components/dashboard_panel.vue' do - element :prometheus_graph_widgets - element :prometheus_widgets_dropdown - element :generate_chart_link_menu_item - end - - view 'app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue' do - element :quick_range_item - end - - view 'app/assets/javascripts/monitoring/components/variables_section.vue' do - element :variables_content - element :variable_item - end - - def wait_for_metrics - wait_for_data - return if has_metrics? - - wait_until(max_duration: 180) do - wait_for_data - has_metrics? - end - end - - def has_metrics? - within_element :prometheus_graphs_content do - has_text?(EXPECTED_TITLE) - end - end - - def has_edit_dashboard_enabled? - click_element :actions_menu_dropdown - - within_element :actions_menu_dropdown do - has_element? :edit_dashboard_button_enabled - end - end - - def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to default branch') - click_element :actions_menu_dropdown - click_on 'Duplicate current dashboard' - fill_element :duplicate_dashboard_filename_field, "#{SecureRandom.hex(8)}-#{save_as}" - choose commit_option - within('.modal-content') { click_button(class: 'btn-success') } - end - - def select_dashboard(dashboard_name) - click_element :dashboards_filter_dropdown - - within_element :dashboards_filter_dropdown do - click_on dashboard_name - end - end - - def filter_environment(environment = 'production') - click_element :environments_dropdown - - within_element :environments_dropdown do - click_link_with_text environment - end - end - - def show_last(range = '8 hours') - all_elements(:range_picker_dropdown, minimum: 1).first.click - click_element :quick_range_item, text: range - end - - def copy_link_to_first_chart - all_elements(:prometheus_widgets_dropdown, minimum: 1).first.click - find_element(:generate_chart_link_menu_item)['data-clipboard-text'] - end - - def has_custom_metric?(metric) - within_element :prometheus_graphs_content do - has_text?(metric) - end - end - - def has_templating_variable?(variable) - within_element :variables_content do - has_element?(:variable_item, text: variable) - end - end - - def has_template_metric?(metric) - within_element :prometheus_graphs_content do - has_text?(metric) - end - end - - private - - def wait_for_data - wait_until(reload: false) { !has_text?(LOADING_MESSAGE) } if has_text?(LOADING_MESSAGE) - end - end - end - end - end - end -end - -QA::Page::Project::Monitor::Metrics::Show.prepend_mod_with('Page::Project::Monitor::Metrics::Show', namespace: QA) diff --git a/qa/qa/page/project/pipeline/new.rb b/qa/qa/page/project/pipeline/new.rb index 742fcad5c07..1d85d072e34 100644 --- a/qa/qa/page/project/pipeline/new.rb +++ b/qa/qa/page/project/pipeline/new.rb @@ -5,23 +5,49 @@ module QA module Project module Pipeline class New < QA::Page::Base - view 'app/assets/javascripts/pipeline_new/components/legacy_pipeline_new_form.vue' do + view 'app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue' do element :run_pipeline_button, required: true element :ci_variable_row_container element :ci_variable_key_field element :ci_variable_value_field + element :ci_variable_value_dropdown + element :ci_variable_value_dropdown_item end def click_run_pipeline_button click_element(:run_pipeline_button, Page::Project::Pipeline::Show) end + def click_variable_dropdown + return unless has_variable_dropdown? + + click_element(:ci_variable_value_dropdown) + end + 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) unless key.nil? fill_element(:ci_variable_value_field, value) end end + + def has_variable_dropdown? + has_element?(:ci_variable_value_dropdown) + end + + def variable_dropdown + return unless has_variable_dropdown? + + find_element(:ci_variable_value_dropdown) + end + + def variable_dropdown_item_with_index(index) + return unless has_variable_dropdown? + + within_element_by_index(:ci_variable_value_dropdown_item, index) do + find('p') + end + end end end end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 33ba27a788a..e4511ababfd 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -11,6 +11,10 @@ module QA element :pipeline_header, required: true end + view 'app/views/projects/pipelines/_info.html.haml' do + element :merge_request_badge_tag + end + view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do element :pipeline_graph, /class.*pipeline-graph.*/ # rubocop:disable QA/ElementWithPattern end @@ -27,7 +31,7 @@ module QA element :downstream_title_content end - view 'app/assets/javascripts/reports/components/report_section.vue' do + view 'app/assets/javascripts/ci/reports/components/report_section.vue' do element :expand_report_button end @@ -46,6 +50,10 @@ module QA end end + def has_merge_request_badge_tag? + has_element?(:merge_request_badge_tag) + end + def has_build?(name, status: :success, wait: nil) if status within_element(:job_item_container, text: name) do diff --git a/qa/qa/page/project/pipeline_editor/new.rb b/qa/qa/page/project/pipeline_editor/new.rb index 5d79dd86f2a..da3e772b11f 100644 --- a/qa/qa/page/project/pipeline_editor/new.rb +++ b/qa/qa/page/project/pipeline_editor/new.rb @@ -5,7 +5,7 @@ module QA module Project module PipelineEditor class New < QA::Page::Base - view 'app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue' do element :create_new_ci_button, required: true end diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb index 8fa20aa57cf..0a7a4460d18 100644 --- a/qa/qa/page/project/pipeline_editor/show.rb +++ b/qa/qa/page/project/pipeline_editor/show.rb @@ -5,21 +5,21 @@ module QA module Project module PipelineEditor class Show < QA::Page::Base - view 'app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue' do + view 'app/assets/javascripts/ci/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 + view 'app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue' do element :branch_selector_button, required: true element :branch_menu_item_button element :branch_menu_container end - view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/commit/commit_form.vue' do element :source_branch_field, required: true end - view 'app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/editor/ci_editor_header.vue' do element :drawer_toggle, required: true element :template_repo_link, required: true end @@ -28,16 +28,16 @@ module QA element :source_editor_container, required: true end - view 'app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/header/pipeline_status.vue' do element :pipeline_id_content end - view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/commit/commit_form.vue' do element :commit_changes_button element :new_mr_checkbox end - view 'app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/header/validation_segment.vue' do element :validation_message_content end @@ -46,15 +46,15 @@ module QA element :job_container end - view 'app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/pipeline_editor_tabs.vue' do element :file_editor_container end - view 'app/assets/javascripts/pipeline_editor/components/popovers/file_tree_popover.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/popovers/file_tree_popover.vue' do element :file_tree_popover end - view 'app/assets/javascripts/pipeline_editor/components/validate/ci_validate.vue' do + view 'app/assets/javascripts/ci/pipeline_editor/components/validate/ci_validate.vue' do element :simulate_pipeline_button end diff --git a/qa/qa/page/project/settings/alerts.rb b/qa/qa/page/project/settings/alerts.rb index be9b61ded80..a74a227d697 100644 --- a/qa/qa/page/project/settings/alerts.rb +++ b/qa/qa/page/project/settings/alerts.rb @@ -6,14 +6,27 @@ module QA module Settings class Alerts < Page::Base view 'app/assets/javascripts/alerts_settings/components/alerts_form.vue' do - element :create_issue_checkbox + element :create_incident_checkbox element :incident_templates_dropdown element :save_changes_button element :incident_templates_item end - def enable_issues_for_incidents - check_element(:create_issue_checkbox) + view 'app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue' do + element :add_integration_button + end + + view 'app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue' do + element :integration_type_dropdown + element :integration_name_field + element :active_toggle_container + element :save_and_create_alert_button + element :test_payload_field + element :send_test_alert_button + end + + def enable_incident_for_alert + check_element(:create_incident_checkbox) end def select_issue_template(template) @@ -32,6 +45,43 @@ module QA has_text?(template) end end + + def add_new_integration + wait_for_requests + click_element(:add_integration_button) + end + + def select_http_endpoint + click_element(:integration_type_dropdown) + find("option[value='HTTP']").click + + # Click outside of the list to close it + click_element(:integration_name_field) + end + + def enter_integration_name(name) + fill_element(:integration_name_field, name) + end + + def activate_integration + within_element(:active_toggle_container) do + find('.gl-toggle').click + end + + wait_for_requests + end + + def save_and_create_alert + click_element(:save_and_create_alert_button) + end + + def fill_in_test_payload(payload) + fill_element(:test_payload_field, payload) + end + + def send_test_alert + click_element(:send_test_alert_button) + 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 d862979aeec..ae6a04028c9 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -15,7 +15,7 @@ module QA element :merge_ff_radio end - view 'app/views/projects/_merge_request_merge_checks_settings.html.haml' do + view 'app/views/projects/_merge_request_pipelines_and_threads_options.html.haml' do element :allow_merge_if_all_discussions_are_resolved_checkbox end diff --git a/qa/qa/page/project/settings/monitor.rb b/qa/qa/page/project/settings/monitor.rb index 87fb0698897..8170ae31a13 100644 --- a/qa/qa/page/project/settings/monitor.rb +++ b/qa/qa/page/project/settings/monitor.rb @@ -11,8 +11,18 @@ module QA element :incidents_settings_content end + view 'app/views/projects/settings/operations/_alert_management.html.haml' do + element :alerts_settings_content + end + def expand_incidents(&block) expand_content(:incidents_settings_content) do + # Fill in with incidents settings + end + end + + def expand_alerts(&block) + expand_content(:alerts_settings_content) do Settings::Alerts.perform(&block) end end diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 4fbf656210f..659fe198d49 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -5,19 +5,19 @@ module QA module Project module Settings class ProtectedBranches < Page::Base - view 'app/views/projects/protected_branches/shared/_dropdown.html.haml' do + view 'app/views/protected_branches/shared/_dropdown.html.haml' do element :protected_branch_dropdown element :protected_branch_dropdown_content end - view 'app/views/projects/protected_branches/_create_protected_branch.html.haml' do + view 'app/views/protected_branches/_create_protected_branch.html.haml' do element :allowed_to_push_dropdown element :allowed_to_push_dropdown_content element :allowed_to_merge_dropdown element :allowed_to_merge_dropdown_content end - view 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml' do + view 'app/views/protected_branches/shared/_create_protected_branch.html.haml' do element :protect_button end diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index bf1c3130485..6931d26b259 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -7,7 +7,7 @@ module QA class Repository < Page::Base include QA::Page::Settings::Common - view 'app/views/projects/protected_branches/shared/_index.html.haml' do + view 'app/views/protected_branches/shared/_index.html.haml' do element :protected_branches_settings_content end diff --git a/qa/qa/page/project/settings/services/jenkins.rb b/qa/qa/page/project/settings/services/jenkins.rb index 39403995ce8..a9b5c84f9ee 100644 --- a/qa/qa/page/project/settings/services/jenkins.rb +++ b/qa/qa/page/project/settings/services/jenkins.rb @@ -13,7 +13,7 @@ module QA element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern end - view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do + view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do element :save_changes_button end diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb index 41034bbd897..7a62b111f98 100644 --- a/qa/qa/page/project/settings/services/jira.rb +++ b/qa/qa/page/project/settings/services/jira.rb @@ -19,7 +19,7 @@ module QA element :service_jira_issue_transition_id_field end - view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do + view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do element :save_changes_button end diff --git a/qa/qa/page/project/settings/services/pipeline_status_emails.rb b/qa/qa/page/project/settings/services/pipeline_status_emails.rb index 2f78577e3d5..3edd1d61d76 100644 --- a/qa/qa/page/project/settings/services/pipeline_status_emails.rb +++ b/qa/qa/page/project/settings/services/pipeline_status_emails.rb @@ -9,6 +9,9 @@ module QA view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do element :recipients_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern element :notify_only_broken_pipelines_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern + end + + view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do element :save_changes_button end diff --git a/qa/qa/page/project/settings/services/prometheus.rb b/qa/qa/page/project/settings/services/prometheus.rb deleted file mode 100644 index 2e3c385a27d..00000000000 --- a/qa/qa/page/project/settings/services/prometheus.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Settings - module Services - class Prometheus < Page::Base - include Page::Component::CustomMetric - - view 'app/views/shared/integrations/prometheus/_custom_metrics.html.haml' do - element :custom_metrics_container - element :new_metric_button - end - - def click_on_custom_metric(custom_metric) - within_element :custom_metrics_container do - click_on custom_metric - end - end - - def click_on_new_metric - click_element :new_metric_button - end - - def has_custom_metric?(custom_metric) - within_element :custom_metrics_container do - has_text? custom_metric - end - end - end - end - end - end - end -end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index a82fa7f5cf3..168bfd6aa0a 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -36,7 +36,6 @@ module QA end view 'app/views/projects/_home_panel.html.haml' do - element :forked_from_link element :project_name_content element :project_id_content element :project_badges_content @@ -48,6 +47,10 @@ module QA element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern end + view 'app/views/projects/_fork_info.html.haml' do + element :forked_from_link + end + view 'app/views/projects/buttons/_fork.html.haml' do element :fork_label, "%span= s_('ProjectOverview|Fork')" # rubocop:disable QA/ElementWithPattern element :fork_link, "link_to new_project_fork_path(@project)" # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/page/project/sub_menus/monitor.rb b/qa/qa/page/project/sub_menus/monitor.rb index e3593e0a257..27fb58fb146 100644 --- a/qa/qa/page/project/sub_menus/monitor.rb +++ b/qa/qa/page/project/sub_menus/monitor.rb @@ -15,18 +15,18 @@ module QA end end - def go_to_monitor_metrics + def go_to_monitor_incidents hover_monitor do within_submenu do - click_element(:sidebar_menu_item_link, menu_item: 'Metrics') + click_element(:sidebar_menu_item_link, menu_item: 'Incidents') end end end - def go_to_monitor_incidents + def go_to_monitor_alerts hover_monitor do within_submenu do - click_element(:sidebar_menu_item_link, menu_item: 'Incidents') + click_element(:sidebar_menu_item_link, menu_item: 'Alerts') end end end diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb index 293fcd1e676..975d3c8ea14 100644 --- a/qa/qa/page/project/web_ide/edit.rb +++ b/qa/qa/page/project/web_ide/edit.rb @@ -109,6 +109,14 @@ module QA element :file_to_commit_content end + # Used for stablility, due to feature_caching of vscode_web_ide + def wait_until_ide_loads + Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page, + retry_on_exception: true) do + has_element?(:commit_mode_tab) + end + end + def has_file?(file_name) within_element(:file_list_container) do has_element?(:file_name_content, file_name: file_name) diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index d1cfdfbc16c..d82109c1d54 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -170,9 +170,7 @@ module QA end def api_client - @api_client ||= begin - Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'), user: api_user) - end + @api_client ||= Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'), user: api_user) end def process_api_response(parsed_response) diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 4a1a60f4da1..00c002cae9c 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -23,7 +23,7 @@ module QA end def fabricate_via_api_unless_fips! - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? fabricate! else fabricate_via_api! @@ -31,7 +31,7 @@ module QA end def fabricate!(*args, &prepare_block) - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? fabricate_via_browser_ui!(*args, &prepare_block) else fabricate_via_api!(*args, &prepare_block) @@ -107,7 +107,7 @@ module QA Support::FabricationTracker.save_fabrication(:"#{fabrication_method}_fabrication", fabrication_time) - unless resource.retrieved_from_cache || QA::Support::FIPS.enabled? + unless resource.retrieved_from_cache || Runtime::Env.personal_access_tokens_disabled? Tools::TestResourceDataProcessor.collect( resource: resource, info: resource.identifier, diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb index 31db8ae4cc6..19ad5f1faf2 100644 --- a/qa/qa/resource/bulk_import_group.rb +++ b/qa/qa/resource/bulk_import_group.rb @@ -11,7 +11,7 @@ module QA api_client.personal_access_token end - attribute :gitlab_address do + attribute :source_gitlab_address do QA::Runtime::Scenario.gitlab_address end @@ -28,7 +28,7 @@ module QA Page::Group::New.perform do |group| group.switch_to_import_tab - group.connect_gitlab_instance(gitlab_address, import_access_token) + group.connect_gitlab_instance(source_gitlab_address, import_access_token) end Page::Group::BulkImport.perform do |import_page| @@ -50,7 +50,7 @@ module QA def api_post_body { configuration: { - url: gitlab_address, + url: source_gitlab_address, access_token: import_access_token }, entities: [ @@ -93,7 +93,7 @@ module QA # override transformation only for /bulk_imports endpoint which doesn't have web_url in response and # ignore others so import_id is not overwritten incorrectly - api_resource[:web_url] = "#{gitlab_address}/#{full_path}" + api_resource[:web_url] = "#{QA::Runtime::Scenario.gitlab_address}/#{full_path}" api_resource[:import_id] = api_resource[:id] api_resource end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 9d1a6868562..f53bb531d9a 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -18,7 +18,7 @@ module QA end attribute :sandbox do - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? Resource::Sandbox.fabricate! do |sandbox| sandbox.path = Runtime::Namespace.sandbox_name end @@ -40,9 +40,7 @@ module QA sandbox.visit! Page::Group::Show.perform do |group_show| - if group_show.has_subgroup?(path) - group_show.click_subgroup(path) - else + unless group_show.has_subgroup?(path) group_show.go_to_new_subgroup Page::Group::New.perform do |group_new| @@ -56,7 +54,11 @@ module QA group_show.has_text?(path) && group_show.has_new_project_and_new_subgroup_buttons? end + sandbox.visit! end + + group_show.click_subgroup(path) + @id = group_show.group_id end end diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index f6d1aacca0a..c5b1a4ecea0 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -24,6 +24,7 @@ module QA def projects parse_body(api_get_from("#{api_get_path}/projects")).map do |project| Project.init do |resource| + resource.add_name_uuid = false resource.api_client = api_client resource.group = self resource.id = project[:id] diff --git a/qa/qa/resource/issuable.rb b/qa/qa/resource/issuable.rb index 6ebdaac8298..5aee27b46d4 100644 --- a/qa/qa/resource/issuable.rb +++ b/qa/qa/resource/issuable.rb @@ -3,6 +3,8 @@ module QA module Resource class Issuable < Base + using Rainbow + # Commentes (notes) path # # @return [String] @@ -14,6 +16,7 @@ module QA # # @return [Array] def comments(auto_paginate: false, attempts: 0) + Runtime::Logger.debug("Fetching comments for #{self.class.name.black.bg(:white)} with path '#{api_get_path}'") return parse_body(api_get_from(api_comments_path)) unless auto_paginate auto_paginated_response( diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index fcfda106523..d1d99393ca2 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -199,6 +199,10 @@ module QA :source_project_id, :target_project_id, :merge_status, + # we consider mr to still be the same even if users changed + :author, + :reviewers, + :assignees, # these can differ depending on user fetching mr :user, :subscribed, diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb index 7b6a3d296c4..879c3a4282c 100644 --- a/qa/qa/resource/protected_branch.rb +++ b/qa/qa/resource/protected_branch.rb @@ -51,8 +51,8 @@ module QA page.select_allowed_to_merge(allowed_to_merge) page.select_allowed_to_push(allowed_to_push) page.protect_branch - else - page.require_code_owner_approval(branch_name) if require_code_owner_approval + elsif require_code_owner_approval + page.require_code_owner_approval(branch_name) end end end diff --git a/qa/qa/resource/reusable.rb b/qa/qa/resource/reusable.rb index 6a9d0392ba2..536f70b50b4 100644 --- a/qa/qa/resource/reusable.rb +++ b/qa/qa/resource/reusable.rb @@ -37,8 +37,8 @@ module QA resource: self } - self.class.resources[reuse_as][:attributes] ||= all_attributes.each_with_object({}) do |attribute_name, attributes| - attributes[attribute_name] = instance_variable_get("@#{attribute_name}") + self.class.resources[reuse_as][:attributes] ||= all_attributes.index_with do |attribute_name| + instance_variable_get("@#{attribute_name}") end self.class.resources[reuse_as][:tests] << Runtime::Example.location end diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index da4021f89b7..3c74d8de21a 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -3,10 +3,22 @@ module QA module Resource class Runner < Base - attr_writer :name, :tags, :image, :executor, :executor_image - attr_accessor :config, :token, :run_untagged + attributes :id, + :active, + :paused, + :runner_type, + :online, + :status, + :ip_address, + :token, + :tags, + :config, + :run_untagged, + :name, # This attribute == runner[:description] + :image, + :executor, + :executor_image - attribute :id attribute :project do Project.fabricate_via_api! do |resource| resource.name = 'project-with-ci-cd' @@ -14,81 +26,47 @@ module QA end end - def name - @name || "qa-runner-#{SecureRandom.hex(4)}" + def initialize + @tags = nil + @config = nil + @run_untagged = nil + @name = "qa-runner-#{SecureRandom.hex(4)}" + @image = 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine' + @executor = :shell + @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7' end - def image - @image || 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine' - end - - def executor - @executor || :shell - end - - def executor_image - @executor_image || 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7' + # Initially we only support fabricate + # via API + def fabricate! + fabricate_via_api! end + # Start container and register runner + # Fetch via API and populate attributes + # def fabricate_via_api! - @docker_container = Service::DockerRun::GitlabRunner.new(name).tap do |runner| - QA::Support::Retrier.retry_on_exception(sleep_interval: 5) do - runner.pull - end - - runner.token = @token ||= project.runners_token - runner.address = Runtime::Scenario.gitlab_address - runner.tags = @tags if @tags - runner.image = image - runner.config = config if config - runner.executor = executor - runner.executor_image = executor_image if executor == :docker - runner.run_untagged = run_untagged if run_untagged - runner.register! - end + start_container_and_register + populate_runner_attributes end def remove_via_api! - runners = list_of_runners(tag_list: @tags) - - # If we have no runners, print the logs from the runner docker container in case they show why it isn't running. - if runners.blank? - dump_logs - - return - end - - this_runner = runners.find { |runner| runner[:description] == name } - - # As above, but now we should have a specific runner. If not, print the logs from the runner docker container - # to see if we can find out why the runner isn't running. - unless this_runner - dump_logs - - raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} #{"or tags #{@tags}" if @tags&.any?}. Runners available: #{runners}" - end - - @id = this_runner[:id] - super ensure - Service::DockerRun::GitlabRunner.new(name).remove! - end - - def list_of_runners(tag_list: nil) - url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path - auto_paginated_response(request_url(url)) + @docker_container.remove! end def reload! - super if method(:running?).super_method.call + populate_runner_attributes end def api_delete_path "/runners/#{id}" end - def api_get_path; end + def api_get_path + "/runners" + end def api_post_path "/runners" @@ -96,15 +74,75 @@ module QA def api_post_body; end + def not_found_by_tags? + url = "#{api_get_path}?tag_list=#{tags.compact.join(',')}" + auto_paginated_response(request_url(url)).empty? + end + + def runners_list + runners_list = nil + url = tags ? "#{api_get_path}?tag_list=#{tags.compact.join(',')}" : api_get_path + Runtime::Logger.info('Looking for list of runners via API...') + Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do + runners_list = auto_paginated_response(request_url(url)) + runners_list.present? + end + + runners_list + end + + def wait_until_online + Runtime::Logger.info('Waiting for runner to come online...') + Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do + this_runner[:status] == 'online' + end + end + + def restart + Runtime::Logger.info("Restarting runner container #{name}...") + @docker_container.restart + wait_until_online + end + private - def dump_logs - if @docker_container.running? - @docker_container.logs - else - QA::Runtime::Logger.debug("No runner container found named #{name}") + def start_container_and_register + @docker_container = Service::DockerRun::GitlabRunner.new(name).tap do |runner| + Support::Retrier.retry_on_exception(sleep_interval: 5) do + runner.pull + end + + runner.token = @token ||= project.runners_token + runner.address = Runtime::Scenario.gitlab_address + runner.tags = tags if tags + runner.image = image + runner.config = config if config + runner.executor = executor + runner.executor_image = executor_image if executor == :docker + runner.run_untagged = run_untagged if run_untagged + runner.register! end end + + def this_runner + runner = nil + Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do + runner = runners_list.find { |runner| runner[:description] == name } + !runner.nil? + end + runner + end + + def populate_runner_attributes + runner = this_runner + @id = runner[:id] + @active = runner[:active] + @paused = runner[:paused] + @runner_type = runner[:typed] + @online = runner[:online] + @status = runner[:status] + @ip_address = runner[:ip_address] + end end end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 18526448b00..f5cd51bf9cf 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -10,7 +10,7 @@ module QA class << self # Force top level group creation via UI if test is executed on dot_com environment def fabricate!(*args, &prepare_block) - if Specs::Helpers::ContextSelector.dot_com? || QA::Support::FIPS.enabled? + if Specs::Helpers::ContextSelector.dot_com? || Runtime::Env.personal_access_tokens_disabled? return fabricate_via_browser_ui!(*args, &prepare_block) end diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb index dd475d7fa66..1c142058908 100644 --- a/qa/qa/resource/ssh_key.rb +++ b/qa/qa/resource/ssh_key.rb @@ -3,14 +3,12 @@ module QA module Resource class SSHKey < Base - extend Forwardable - attr_reader :title attr_accessor :expires_at attribute :id - def_delegators :key, :private_key, :public_key, :md5_fingerprint, :sha256_fingerprint + delegate :private_key, :public_key, :md5_fingerprint, :sha256_fingerprint, to: :key def initialize self.title = Time.now.to_f diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index c8babbc0b16..0398509396f 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -155,7 +155,7 @@ module QA end def self.fabricate_or_use(username = nil, password = nil) - if Runtime::Env.signup_disabled? || !QA::Support::FIPS.enabled? + if Runtime::Env.signup_disabled? && !Runtime::Env.personal_access_tokens_disabled? fabricate_via_api! do |user| user.username = username user.password = password diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 5ca3b0c51f8..213388ca264 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -35,9 +35,12 @@ module QA end def self.as_admin - @admin_client ||= begin + @admin_client ||= if Runtime::Env.admin_personal_access_token - Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) + Runtime::API::Client.new( + :gitlab, + personal_access_token: Runtime::Env.admin_personal_access_token + ) else # To return an API client that has admin access, we need a user with admin access to confirm that # the API client user has admin access. @@ -62,7 +65,6 @@ module QA client end - end end private diff --git a/qa/qa/runtime/api/repository_storage_moves.rb b/qa/qa/runtime/api/repository_storage_moves.rb index fb8d70c0836..450b7cd5712 100644 --- a/qa/qa/runtime/api/repository_storage_moves.rb +++ b/qa/qa/runtime/api/repository_storage_moves.rb @@ -16,7 +16,7 @@ module QA QA::Runtime::Logger.debug("Move data: #{move}") move[:state] == status && - move[:destination_storage_name] == destination_storage + move[:destination_storage_name] == destination_storage end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index d2ddaf86353..af1a4e06473 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -160,15 +160,14 @@ module QA # From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326 Capybara::Screenshot.register_driver(QA::Runtime::Env.browser) do |driver, path| + QA::Runtime::Logger.info("Saving screenshot..") driver.browser.save_screenshot(path) end - 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] + example.full_description.downcase.parameterize(separator: "_")[0..79] ) end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 7cb7625118e..d4d9ffe62e6 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -133,6 +133,11 @@ module QA enabled?(ENV['SIGNUP_DISABLED'], default: false) end + # PATs are disabled for FedRamp + def personal_access_tokens_disabled? + enabled?(ENV['PERSONAL_ACCESS_TOKENS_DISABLED'], default: false) + end + def admin_password ENV['GITLAB_ADMIN_PASSWORD'] end @@ -431,7 +436,7 @@ module QA end def gitlab_agentk_version - ENV.fetch('GITLAB_AGENTK_VERSION', 'v14.5.0') + ENV.fetch('GITLAB_AGENTK_VERSION', 'fe716ea') end def transient_trials @@ -493,6 +498,10 @@ module QA enabled?(ENV['QA_USE_PUBLIC_IP_API'], default: false) end + def allow_local_requests? + enabled?(ENV['QA_ALLOW_LOCAL_REQUESTS'], default: false) + end + def chrome_default_download_path ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] end diff --git a/qa/qa/runtime/ip_address.rb b/qa/qa/runtime/ip_address.rb index fcb6db750bb..ae83d10ffb5 100644 --- a/qa/qa/runtime/ip_address.rb +++ b/qa/qa/runtime/ip_address.rb @@ -8,16 +8,17 @@ module QA HostUnreachableError = Class.new(StandardError) LOOPBACK_ADDRESS = '127.0.0.1' - PUBLIC_IP_ADDRESS_API = "https://api.ipify.org" + PUBLIC_IP_ADDRESS_API = 'https://api.ipify.org' 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 - non_test_host = !URI.parse(Scenario.gitlab_address).host.include?('.test') + non_test_host = !URI.parse(Scenario.gitlab_address).host.include?('.test') # rubocop:disable Rails/NegateInclude has_no_public_ip = Env.running_in_ci? || Env.use_public_ip_api? ip_address = if has_no_public_ip && non_test_host - response = get(PUBLIC_IP_ADDRESS_API) + response = get_public_ip_address + raise HostUnreachableError, "#{PUBLIC_IP_ADDRESS_API} is unreachable" unless response.code == Support::API::HTTP_STATUS_OK response.body @@ -31,6 +32,12 @@ module QA ip_address end + + def get_public_ip_address + Support::Retrier.retry_on_exception(sleep_interval: 1) do + get(PUBLIC_IP_ADDRESS_API) + end + end end end end diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb index e0e7385d6d4..7e78ba470d8 100644 --- a/qa/qa/runtime/logger.rb +++ b/qa/qa/runtime/logger.rb @@ -1,23 +1,21 @@ # frozen_string_literal: true -require 'forwardable' - module QA module Runtime class Logger - extend SingleForwardable - - def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown + class << self + # Global logger instance + # + # @return [ActiveSupport::Logger] + def logger + @logger ||= Gitlab::QA::TestLogger.logger( + level: Gitlab::QA::Runtime::Env.log_level, + source: 'QA Tests', + path: File.expand_path('../../tmp', __dir__) + ) + end - # Global logger instance - # - # @return [ActiveSupport::Logger] - def self.logger - @logger ||= Gitlab::QA::TestLogger.logger( - level: Gitlab::QA::Runtime::Env.log_level, - source: 'QA Tests', - path: File.expand_path('../../tmp', __dir__) - ) + delegate :debug, :info, :warn, :error, :fatal, :unknown, to: :logger end end end diff --git a/qa/qa/runtime/script_extensions/interceptor.js b/qa/qa/runtime/script_extensions/interceptor.js index 9e98b0421b4..cde94e98774 100644 --- a/qa/qa/runtime/script_extensions/interceptor.js +++ b/qa/qa/runtime/script_extensions/interceptor.js @@ -101,6 +101,34 @@ } /** + * @param url - the URL + * @param method - the REST method + * @param clonedResponse - a cloned fetch response + * @return {Promise<void>} + */ + async function checkForGraphQLErrors(url, method, clonedResponse) { + if (/api\/graphql/.test(url)) { + const body = await clonedResponse.json(); + if (body.errors && body.errors instanceof Array) { + const errorMessages = body.errors.map((error) => error.message); + + commitToCache((cache) => { + // eslint-disable-next-line no-param-reassign + cache.errors ||= []; + cache.errors.push({ + status: clonedResponse.status, + url, + method, + errorData: `error-messages: ${errorMessages.join(', ')}`, + headers: { 'x-request-id': clonedResponse.headers.get('x-request-id') }, + }); + return cache; + }); + } + } + } + + /** * Replacement for fetch implementation * tracks active requests, and commits metadata to the cache * if the response is not ok or was cancelled. @@ -115,7 +143,6 @@ window.Interceptor.activeFetchRequests += 1; try { const response = await pureFetch(url, opts, ...args); - window.Interceptor.activeFetchRequests += -1; const clone = response.clone(); if (!clone.ok) { @@ -131,6 +158,9 @@ return cache; }); } + + await checkForGraphQLErrors(url, method, clone); + return response; } catch (error) { commitToCache((cache) => { @@ -144,14 +174,24 @@ return cache; }); - window.Interceptor.activeFetchRequests += -1; throw error; + } finally { + window.Interceptor.activeFetchRequests += -1; } } - if (checkCache()) { - saveCache({}); - } + /** + * Initializes the cache + * if the cache doesn't already exist. + */ + const initCache = () => { + if (checkCache() && getCache() == null) { + saveCache({}); + } + }; + + // Initialize cache on page load. + initCache(); window.fetch = interceptedFetch; window.XMLHttpRequest.prototype.open = interceptXhr; diff --git a/qa/qa/scenario/test/integration/import.rb b/qa/qa/scenario/test/integration/import.rb new file mode 100644 index 00000000000..4b0966998cd --- /dev/null +++ b/qa/qa/scenario/test/integration/import.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class Import < Test::Instance::All + tags :import + end + end + end + end +end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb index c91f68d31a0..65ebe09eeea 100644 --- a/qa/qa/service/docker_run/base.rb +++ b/qa/qa/service/docker_run/base.rb @@ -77,6 +77,12 @@ module QA def read_file(file_path) `docker exec #{@name} /bin/cat #{file_path}` end + + def restart + return "Container #{@name} is not running, cannot restart." unless running? + + shell "docker restart #{@name}" + end end end end diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index 45ab4ceff99..7a62951a2f6 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -56,6 +56,12 @@ module QA @run_untagged = false end + def restart + super + + wait_until_shell_command_matches("docker logs #{@name}", /Configuration loaded/) + end + private def register_command diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index c332e7a6198..57f5310901b 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -9,6 +9,8 @@ module QA attr_accessor :gitlab + attr_reader :primary_node, :secondary_node, :tertiary_node, :postgres + PrometheusQueryError = Class.new(StandardError) def initialize @@ -21,7 +23,9 @@ module QA @virtual_storage = 'default' end - attr_reader :primary_node, :secondary_node, :tertiary_node, :postgres + def gitaly_nodes + [primary_node, secondary_node, tertiary_node] + end # Executes the praefect `dataloss` command. # @@ -50,42 +54,22 @@ module QA end end - def stop_primary_node - stop_node(@primary_node) - wait_until_node_is_removed_from_healthy_storages(@primary_node) - end - - def start_primary_node - start_node(@primary_node) - end - def start_praefect start_node(@praefect) - wait_for_praefect + QA::Runtime::Logger.info("Waiting for health check on praefect") + Support::Waiter.wait_until(max_duration: 120, sleep_interval: 1, raise_on_failure: true) do + wait_until_shell_command("docker exec #{@praefect} gitlab-ctl status praefect") do |line| + break true if line.include?('run: praefect: ') + + QA::Runtime::Logger.debug(line.chomp) + end + end end def stop_praefect stop_node(@praefect) end - def stop_secondary_node - stop_node(@secondary_node) - wait_until_node_is_removed_from_healthy_storages(@secondary_node) - end - - def start_secondary_node - start_node(@secondary_node) - end - - def stop_tertiary_node - stop_node(@tertiary_node) - wait_until_node_is_removed_from_healthy_storages(@tertiary_node) - end - - def start_tertiary_node - start_node(@tertiary_node) - end - def start_node(name) state = node_state(name) return if state == "running" @@ -111,6 +95,8 @@ module QA return if node_state(name) == 'paused' shell "docker pause #{name}" + + wait_until_node_is_removed_from_healthy_storages(name) if gitaly_nodes.include?(name) end def node_state(name) @@ -126,9 +112,9 @@ module QA QA::Runtime::Logger.info("Clearing the replication queue") shell sql_to_docker_exec_cmd( <<~SQL - delete from replication_queue_job_lock; - delete from replication_queue_lock; - delete from replication_queue; + delete from replication_queue_job_lock; + delete from replication_queue_lock; + delete from replication_queue; SQL ) end @@ -137,32 +123,16 @@ module QA QA::Runtime::Logger.info("Setting jobs in replication queue to `in_progress` and acquiring locks") shell sql_to_docker_exec_cmd( <<~SQL - update replication_queue set state = 'in_progress'; - insert into replication_queue_job_lock (job_id, lock_id, triggered_at) - select id, rq.lock_id, created_at from replication_queue rq - left join replication_queue_job_lock rqjl on rq.id = rqjl.job_id - where state = 'in_progress' and rqjl.job_id is null; - update replication_queue_lock set acquired = 't'; + update replication_queue set state = 'in_progress'; + insert into replication_queue_job_lock (job_id, lock_id, triggered_at) + select id, rq.lock_id, created_at from replication_queue rq + left join replication_queue_job_lock rqjl on rq.id = rqjl.job_id + where state = 'in_progress' and rqjl.job_id is null; + update replication_queue_lock set acquired = 't'; SQL ) end - # Reconciles the previous primary node with the current one - # I.e., it brings the previous primary node up-to-date - def reconcile_nodes - reconcile_node_with_node(@primary_node, current_primary_node) - end - - def reconcile_node_with_node(target, reference) - QA::Runtime::Logger.info("Reconcile #{target} with #{reference} on #{@virtual_storage}") - wait_until_shell_command_matches( - "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml reconcile -virtual #{@virtual_storage} -target #{target} -reference #{reference} -f'", - /FINISHED: \d+ repos were checked for consistency/, - sleep_interval: 5, - retry_on_exception: true - ) - end - def query_read_distribution cmd = "docker exec #{@gitlab} bash -c 'curl -s http://localhost:9090/api/v1/query?query=gitaly_praefect_read_distribution'" output = shell(cmd, stream_progress: false) do |line| @@ -173,6 +143,8 @@ module QA raise PrometheusQueryError, "Unable to query read distribution metrics" unless result['status'] == 'success' + raise PrometheusQueryError, "No read distribution metrics found" if result['data']['result'].empty? + result['data']['result'].map { |result| { node: result['metric']['storage'], value: result['value'][1].to_i } } end @@ -202,9 +174,7 @@ module QA def start_all_nodes start_postgres - start_node(@primary_node) - start_node(@secondary_node) - start_node(@tertiary_node) + gitaly_nodes.each { |node| start_node(node) } start_praefect wait_for_health_check_all_nodes @@ -228,17 +198,6 @@ module QA destination_storage[:type] == :praefect ? verify_storage_move_to_praefect(repo_path, destination_storage[:name]) : verify_storage_move_to_gitaly(repo_path, destination_storage[:name]) end - def wait_for_praefect - QA::Runtime::Logger.info("Waiting for health check on praefect") - Support::Waiter.wait_until(max_duration: 120, sleep_interval: 1, raise_on_failure: true) do - wait_until_shell_command("docker exec #{@praefect} gitlab-ctl status praefect") do |line| - break true if line.include?('run: praefect: ') - - QA::Runtime::Logger.debug(line.chomp) - end - end - end - def praefect_sql_ping_healthy? cmd = "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'" wait_until_shell_command(cmd) do |line| @@ -247,17 +206,6 @@ module QA end end - def wait_for_sql_ping - wait_until_shell_command_matches( - "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'", - /praefect sql-ping: OK/ - ) - end - - def health_check_failure_message?(msg) - ['error when pinging healthcheck', 'failed checking node health'].include?(msg) - end - def wait_for_dial_nodes_successful Support::Waiter.repeat_until(max_attempts: 3, max_duration: 120, sleep_interval: 1) do nodes_confirmed = { @@ -314,14 +262,6 @@ module QA dataloss_info end - def praefect_dataloss_info_for_project(project_id) - dataloss_info = [] - Support::Retrier.retry_until(max_duration: 60) do - dataloss_info = praefect_dataloss_information(project_id) - dataloss_info.include?("#{Digest::SHA256.hexdigest(project_id.to_s)}.git") - end - end - def wait_for_project_synced_across_all_storages(project_id) Support::Retrier.retry_until(max_duration: 60) do praefect_dataloss_information(project_id).include?('All repositories are fully available on all assigned storages!') @@ -345,9 +285,7 @@ module QA end def wait_for_health_check_all_nodes - wait_for_gitaly_health_check(@primary_node) - wait_for_gitaly_health_check(@secondary_node) - wait_for_gitaly_health_check(@tertiary_node) + gitaly_nodes.each { |node| wait_for_gitaly_health_check(node) } end def wait_for_gitaly_health_check(node) @@ -362,35 +300,11 @@ module QA wait_until_node_is_marked_as_healthy_storage(node) end - def wait_for_primary_node_health_check - wait_for_gitaly_health_check(@primary_node) - end - - def wait_for_secondary_node_health_check - wait_for_gitaly_health_check(@secondary_node) - end - - def wait_for_tertiary_node_health_check - wait_for_gitaly_health_check(@tertiary_node) - end - def wait_for_health_check_failure(node) QA::Runtime::Logger.info("Waiting for health check failure on #{node}") wait_until_node_is_removed_from_healthy_storages(node) end - def wait_for_primary_node_health_check_failure - wait_for_health_check_failure(@primary_node) - end - - def wait_for_secondary_node_health_check_failure - wait_for_health_check_failure(@secondary_node) - end - - def wait_for_tertiary_node_health_check_failure - wait_for_health_check_failure(@tertiary_node) - end - def wait_until_node_is_removed_from_healthy_storages(node) Support::Waiter.wait_until(max_duration: 120, sleep_interval: 1, raise_on_failure: true) do result = [] @@ -457,10 +371,10 @@ module QA result = [] shell sql_to_docker_exec_cmd( <<~SQL - select job from replication_queue - where state = 'ready' - and job ->> 'change' = 'update' - and job ->> 'target_node_storage' = '#{@primary_node}'; + select job from replication_queue + where state = 'ready' + and job ->> 'change' = 'update' + and job ->> 'target_node_storage' = '#{@primary_node}'; SQL ) do |line| result << line @@ -599,20 +513,6 @@ module QA private - def current_primary_node - result = [] - shell sql_to_docker_exec_cmd("select node_name from shard_primaries where shard_name = '#{@virtual_storage}';") do |line| - result << line - end - # The result looks like: - # node_name - # ----------- - # gitaly1 - # (1 row) - - result[2].strip - end - def dataloss_command "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dataloss'" end @@ -655,13 +555,6 @@ module QA end end - def with_praefect_log(**kwargs) - wait_until_shell_command("docker exec #{@praefect} bash -c 'tail -n 1 /var/log/gitlab/praefect/current'", **kwargs) do |line| - QA::Runtime::Logger.debug(line.chomp) - yield JSON.parse(line) - end - end - def repo_type(repo_path) return :snippet if repo_path.start_with?('@snippets') return :design if repo_path.end_with?('.design.git') diff --git a/qa/qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb index 8bbef4ae429..9ffca8d54c9 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb @@ -25,8 +25,8 @@ module QA it 'automatically fails over', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347830' do # stop other nodes, so we can control which node the commit is sent to - praefect_manager.stop_secondary_node - praefect_manager.stop_tertiary_node + praefect_manager.stop_node(praefect_manager.secondary_node) + praefect_manager.stop_node(praefect_manager.tertiary_node) Resource::Repository::ProjectPush.fabricate! do |push| push.project = project @@ -40,8 +40,8 @@ module QA # Stop the primary node to trigger failover, and then wait # for Gitaly to be ready for writes again - praefect_manager.stop_primary_node - praefect_manager.wait_for_primary_node_health_check_failure + praefect_manager.stop_node(praefect_manager.primary_node) + praefect_manager.wait_for_health_check_failure(praefect_manager.primary_node) Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project @@ -65,8 +65,8 @@ module QA context 'when recovering from dataloss after failover' do it 'automatically reconciles', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347831' do # Start the old primary node again - praefect_manager.start_primary_node - praefect_manager.wait_for_primary_node_health_check + praefect_manager.start_node(praefect_manager.primary_node) + praefect_manager.wait_for_gitaly_health_check(praefect_manager.primary_node) # Confirm automatic reconciliation expect(praefect_manager.replicated?(project.id)).to be true diff --git a/qa/qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb index 1abc7b8a912..022f51205f0 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb @@ -27,8 +27,8 @@ module QA # Stop the primary node to trigger failover, and then wait # for Gitaly to be ready for writes again - praefect_manager.stop_primary_node - praefect_manager.wait_for_primary_node_health_check_failure + praefect_manager.stop_node(praefect_manager.primary_node) + praefect_manager.wait_for_health_check_failure(praefect_manager.primary_node) # Push a commit to the new primary Resource::Repository::ProjectPush.fabricate! do |push| @@ -43,7 +43,7 @@ module QA expect(praefect_manager).to be_replication_pending # Start the old primary node again - praefect_manager.start_primary_node + praefect_manager.start_node(praefect_manager.primary_node) praefect_manager.wait_for_health_check_all_nodes # Wait for automatic replication @@ -51,8 +51,8 @@ module QA # Force switch to the old primary node # This ensures that the commit was replicated - praefect_manager.stop_secondary_node - praefect_manager.stop_tertiary_node + praefect_manager.stop_node(praefect_manager.secondary_node) + praefect_manager.stop_node(praefect_manager.tertiary_node) # Confirm that both commits are available expect(project.commits.map { |commit| commit[:message].chomp }) diff --git a/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb index 397fdb909ac..60ce2a65fd1 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb @@ -36,20 +36,16 @@ module QA context 'when a node is unhealthy' do before do - praefect_manager.stop_secondary_node + praefect_manager.stop_node(praefect_manager.secondary_node) end after do # Leave the cluster in a suitable state for subsequent tests - praefect_manager.start_secondary_node + praefect_manager.start_node(praefect_manager.secondary_node) end it 'does not read from the unhealthy node', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347834', - quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/378174', - type: :flaky - } do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347834' do pre_read_data = praefect_manager.query_read_distribution read_from_project(project, number_of_reads_per_loop * 10) diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb index 6ba192a9dd6..cf387c14006 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb @@ -29,7 +29,7 @@ module QA praefect_manager.wait_for_project_synced_across_all_storages(project.id) # testing for gitaly2 'out of sync' - praefect_manager.stop_secondary_node + praefect_manager.stop_node(praefect_manager.secondary_node) number_of_changes = 3 1.upto(number_of_changes) do |i| @@ -47,7 +47,7 @@ module QA end # testing for gitaly3 'in sync' but marked unhealthy - praefect_manager.stop_tertiary_node + praefect_manager.stop_node(praefect_manager.tertiary_node) project_data_loss = praefect_manager.praefect_dataloss_information(project.id) aggregate_failures "validate dataloss identified" do @@ -74,7 +74,7 @@ module QA end praefect_manager.wait_for_replication_to_node(project.id, praefect_manager.primary_node) - praefect_manager.stop_primary_node + praefect_manager.stop_node(praefect_manager.primary_node) Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.commit_message = 'accept-dataloss-2' @@ -86,7 +86,7 @@ module QA end praefect_manager.wait_for_replication_to_node(project.id, praefect_manager.secondary_node) - praefect_manager.stop_secondary_node + praefect_manager.stop_node(praefect_manager.secondary_node) Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.commit_message = 'accept-dataloss-3' diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb index 94bae38c5c8..f88372c0b59 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb @@ -35,7 +35,7 @@ module QA # During normal operations we avoid create a replication event # https://gitlab.com/groups/gitlab-org/-/epics/7741 - praefect_manager.stop_secondary_node + praefect_manager.stop_node(praefect_manager.secondary_node) Git::Repository.perform do |repository| repository.uri = project.repository_http_location.uri repository.use_default_credentials @@ -47,7 +47,7 @@ module QA end repository.push_all_branches end - praefect_manager.start_secondary_node + praefect_manager.start_node(praefect_manager.secondary_node) Support::Retrier.retry_until(max_duration: 60) do count = praefect_manager.replication_queue_lock_count diff --git a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb index ab50e02c790..27f9bcc9675 100644 --- a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb @@ -4,7 +4,7 @@ module QA # https://github.com/gitlab-qa-github/import-test <- project under test # RSpec.describe 'Manage', product_group: :import do - describe 'GitHub import', :reliable do + describe 'GitHub import' do include_context 'with github import' context 'when imported via api' do diff --git a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb index 5acf15dd2b4..64ab8d8fc43 100644 --- a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb @@ -207,6 +207,8 @@ module QA after do |example| next unless defined?(@import_time) + # add additional import time metric + example.metadata[:custom_test_metrics] = { fields: { import_time: @import_time } } # save data for comparison notification creation save_json( "data", @@ -269,7 +271,7 @@ module QA # fetch all objects right after import has started fetch_github_objects - import_status = lambda do + import_status = -> { imported_project.project_import_status.yield_self do |status| @stats = status.dig(:stats, :imported) @@ -278,7 +280,7 @@ module QA status[:import_status] end - end + } logger.info("== Waiting for import to be finished ==") expect(import_status).to eventually_eq('finished').within(max_duration: import_max_duration, sleep_interval: 30) diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb index e17e12cdaf3..1f0c37df101 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb @@ -1,70 +1,14 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', :reliable, :requires_admin, product_group: :import do - describe 'Gitlab migration' do - let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } - let(:admin_api_client) { Runtime::API::Client.as_admin } - let(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end - end - - let(:api_client) { Runtime::API::Client.new(user: user) } - - let(:sandbox) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client - end - end - - let(:destination_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = sandbox - group.path = "destination-group-for-import-#{SecureRandom.hex(4)}" - end - end - - let(:source_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = sandbox - group.path = "source-group-for-import-#{SecureRandom.hex(4)}" - group.avatar = File.new('qa/fixtures/designs/tanuki.jpg', 'r') - end - end - - let(:imported_group) do - Resource::BulkImportGroup.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = destination_group - group.source_group = source_group - end - end - - let(:import_failures) do - imported_group.import_details.sum([]) { |details| details[:failures] } - end - - before do - sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - end - - after do |example| - # Checking for failures in the test currently makes test very flaky due to catching unrelated failures - # Log failures for easier debugging - Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty? - ensure - user.remove_via_api! - end + RSpec.describe "Manage", :reliable, product_group: :import do + include_context "with gitlab group migration" + describe "Gitlab migration" do context 'with subgroups and labels' do let(:subgroup) do Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client + group.api_client = source_admin_api_client group.sandbox = source_group group.path = "subgroup-for-import-#{SecureRandom.hex(4)}" end @@ -80,12 +24,12 @@ module QA before do Resource::GroupLabel.fabricate_via_api! do |label| - label.api_client = api_client + label.api_client = source_admin_api_client label.group = source_group label.title = "source-group-#{SecureRandom.hex(4)}" end Resource::GroupLabel.fabricate_via_api! do |label| - label.api_client = api_client + label.api_client = source_admin_api_client label.group = subgroup label.title = "subgroup-#{SecureRandom.hex(4)}" end @@ -97,7 +41,7 @@ module QA 'successfully imports groups and labels', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347674' ) do - expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration) + expect_group_import_finished_successfully aggregate_failures do expect(imported_group.reload!).to eq(source_group) @@ -112,7 +56,7 @@ module QA context 'with milestones and badges' do let(:source_milestone) do Resource::GroupMilestone.fabricate_via_api! do |milestone| - milestone.api_client = api_client + milestone.api_client = source_admin_api_client milestone.group = source_group end end @@ -121,7 +65,7 @@ module QA source_milestone Resource::GroupBadge.fabricate_via_api! do |badge| - badge.api_client = api_client + badge.api_client = source_admin_api_client badge.group = source_group badge.link_url = "http://example.com/badge" badge.image_url = "http://shields.io/badge" @@ -134,7 +78,7 @@ module QA 'successfully imports group milestones and badges', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347628' ) do - expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration) + expect_group_import_finished_successfully imported_milestone = imported_group.reload!.milestones.find { |ml| ml.title == source_milestone.title } aggregate_failures do diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index 887eeda51e3..dd2e7f06995 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -7,12 +7,18 @@ module QA let!(:source_issue) do Resource::Issue.fabricate_via_api! do |issue| - issue.api_client = api_client + issue.api_client = source_admin_api_client issue.project = source_project issue.labels = %w[label_one label_two] end end + let(:source_issue_comments) do + source_issue.comments.map do |note| + { **note.except(:id, :noteable_id), author: note[:author].except(:web_url) } + end + end + let(:imported_issues) { imported_projects.first.issues } let(:imported_issue) do @@ -24,6 +30,12 @@ module QA end end + let(:imported_issue_comments) do + imported_issue.comments.map do |note| + { **note.except(:id, :noteable_id), author: note[:author].except(:web_url) } + end + end + context 'with project issues' do let!(:source_comment) { source_issue.add_comment(body: 'This is a test comment!') } @@ -33,19 +45,18 @@ module QA 'successfully imports issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347608' ) do - expect_import_finished + expect_project_import_finished_successfully expect(imported_issues.count).to eq(1) - - aggregate_failures do - expect(imported_issue).to eq(source_issue.reload!) - - expect(imported_comments.count).to eq(1) - expect(imported_comments.first&.fetch(:body)).to include(source_comment[:body]) - end + expect(imported_issue).to eq(source_issue.reload!) + expect(imported_issue_comments).to match_array(source_issue_comments) end end - context "with designs" do + # we can't fabricate things in source instance via UI + context "with designs", quarantine: { + type: :broken, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366592' + } do let!(:source_design) do Flow::Login.sign_in(as: user) @@ -66,7 +77,7 @@ module QA 'successfully imports design', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366449' ) do - expect_import_finished + expect_project_import_finished_successfully expect(imported_issues.count).to eq(1) expect(imported_design.full_path).to eq(source_design.full_path) diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb index 116a00f8385..5fc170435e3 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb @@ -4,113 +4,76 @@ # rubocop:disable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers module QA - RSpec.describe "Manage", requires_admin: 'creates users', only: { job: 'large-gitlab-import' } do - describe "Gitlab migration", product_group: :import do - let(:logger) { Runtime::Logger.logger } - let(:differ) { RSpec::Support::Differ.new(color: true) } - let(:gitlab_group) { ENV['QA_LARGE_IMPORT_GROUP'] || 'gitlab-migration' } - let(:gitlab_project) { ENV['QA_LARGE_IMPORT_REPO'] || 'dri' } - let(:gitlab_source_address) { ENV['QA_LARGE_IMPORT_SOURCE_URL'] || 'https://staging.gitlab.com' } - - let(:import_wait_duration) do - { - max_duration: (ENV['QA_LARGE_IMPORT_DURATION'] || 3600).to_i, - sleep_interval: 30 - } - end - - let(:admin_api_client) { Runtime::API::Client.as_admin } - - # explicitly create PAT via api to not create it via UI in environments where admin token env var is not present - let(:target_api_client) do - Runtime::API::Client.new( - user: user, - personal_access_token: Resource::PersonalAccessToken.fabricate_via_api! do |pat| - pat.api_client = admin_api_client - end.token - ) - end - - let(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - end - end - - let(:source_api_client) do + RSpec.describe "Manage", :skip_live_env, only: { job: "large-gitlab-import" } do + describe "Gitlab migration", orchestrated: false, product_group: :import do + include_context "with gitlab group migration" + + let!(:logger) { Runtime::Logger.logger } + let!(:differ) { RSpec::Support::Differ.new(color: true) } + let!(:source_gitlab_address) { ENV["QA_LARGE_IMPORT_SOURCE_URL"] || "https://gitlab.com" } + let!(:gitlab_source_group) { ENV["QA_LARGE_IMPORT_GROUP"] || "gitlab-migration-large-import-test" } + let!(:gitlab_source_project) { ENV["QA_LARGE_IMPORT_REPO"] || "migration-test-project" } + let!(:import_wait_duration) { { max_duration: (ENV["QA_LARGE_IMPORT_DURATION"] || 3600).to_i, sleep_interval: 30 } } + + let!(:source_admin_user) { "no-op" } + let!(:source_admin_api_client) do Runtime::API::Client.new( - gitlab_source_address, - personal_access_token: ENV["QA_LARGE_IMPORT_GL_TOKEN"], + source_gitlab_address, + personal_access_token: ENV["QA_LARGE_IMPORT_GL_TOKEN"] || raise("missing QA_LARGE_IMPORT_GL_TOKEN variable"), is_new_session: false ) end - let(:sandbox) do + # alias api client because for large import test it's not an actual admin user + let!(:source_api_client) { source_admin_api_client } + + let!(:source_group) do Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client + group.api_client = source_api_client + group.path = gitlab_source_group end end - let(:destination_group) do - Resource::Group.fabricate_via_api! do |group| + # generate unique target group because source group has a static name + let!(:target_sandbox) do + Resource::Sandbox.fabricate_via_api! do |group| group.api_client = admin_api_client - group.sandbox = sandbox - group.path = "imported-group-destination-#{SecureRandom.hex(4)}" + group.path = "qa-sandbox-#{SecureRandom.hex(4)}" end end - # Source group and it's objects + # Source objects # - let(:source_group) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = source_api_client - group.path = gitlab_group - end - end - - let(:source_project) { source_group.projects.find { |project| project.name.include?(gitlab_project) }.reload! } + let(:source_project) { source_group.projects.find { |project| project.name == gitlab_source_project }.reload! } let(:source_branches) { source_project.repository_branches(auto_paginate: true).map { |b| b[:name] } } let(:source_commits) { source_project.commits(auto_paginate: true).map { |c| c[:id] } } let(:source_labels) { source_project.labels(auto_paginate: true).map { |l| l.except(:id) } } let(:source_milestones) { source_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } } let(:source_pipelines) { source_project.pipelines(auto_paginate: true).map { |pp| pp.except(:id, :web_url, :project_id) } } - let(:source_mrs) { fetch_mrs(source_project, source_api_client) } - let(:source_issues) { fetch_issues(source_project, source_api_client) } + let(:source_mrs) { fetch_mrs(source_project, source_api_client, transform_urls: true) } + let(:source_issues) { fetch_issues(source_project, source_api_client, transform_urls: true) } - # Imported group and it's objects + # Imported objects # - let(:imported_group) do - Resource::BulkImportGroup.fabricate_via_api! do |group| - group.import_access_token = source_api_client.personal_access_token # token for importing on source instance - group.api_client = target_api_client # token used by qa framework to access resources in destination instance - group.gitlab_address = gitlab_source_address - group.source_group = source_group - group.sandbox = destination_group - end - end - - let(:imported_project) { imported_group.projects.find { |project| project.name.include?(gitlab_project) }.reload! } + let(:imported_project) { imported_group.projects.find { |project| project.name == gitlab_source_project }.reload! } let(:branches) { imported_project.repository_branches(auto_paginate: true).map { |b| b[:name] } } let(:commits) { imported_project.commits(auto_paginate: true).map { |c| c[:id] } } let(:labels) { imported_project.labels(auto_paginate: true).map { |l| l.except(:id) } } let(:milestones) { imported_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } } - let(:pipelines) { imported_project.pipelines.map { |pp| pp.except(:id, :web_url, :project_id) } } - let(:mrs) { fetch_mrs(imported_project, target_api_client) } - let(:issues) { fetch_issues(imported_project, target_api_client) } - - let(:import_failures) { imported_group.import_details.sum([]) { |details| details[:failures] } } + let(:pipelines) { imported_project.pipelines(auto_paginate: true).map { |pp| pp.except(:id, :web_url, :project_id) } } + let(:mrs) { fetch_mrs(imported_project, api_client) } + let(:issues) { fetch_issues(imported_project, api_client) } before do - destination_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + Runtime::Feature.enable(:bulk_import_projects) unless Runtime::Feature.enabled?(:bulk_import_projects) end # rubocop:disable RSpec/InstanceVariable after do |example| - # Log failures for easier debugging - Runtime::Logger.error("Import failures: #{import_failures}") if example.exception && !import_failures.empty? - next unless defined?(@import_time) + # add additional import time metric + example.metadata[:custom_test_metrics] = { fields: { import_time: @import_time } } # save data for comparison notification creation save_json( "data", @@ -121,7 +84,7 @@ module QA source: { name: "GitLab Source", project_name: source_project.path_with_namespace, - address: gitlab_source_address, + address: source_gitlab_address, data: { branches: source_branches.length, commits: source_commits.length, @@ -163,17 +126,14 @@ module QA start = Time.now # trigger import and log imported group path - logger.info("== Importing group '#{gitlab_group}' in to '#{imported_group.full_path}' ==") + logger.info("== Importing group '#{gitlab_source_group}' in to '#{imported_group.full_path}' ==") # fetch all objects right after import has started fetch_source_gitlab_objects # wait for import to finish and save import time logger.info("== Waiting for import to be finished ==") - expect { imported_group.import_status }.not_to eventually_eq("started").within(import_wait_duration) - # finished status actually means success, don't wait for finished status explicitly - # because test would wait full duration if returned status is "failed" - expect(imported_group.import_status).to eq("finished") + expect_group_import_finished_successfully @import_time = Time.now - start @@ -267,8 +227,8 @@ module QA comment_diff = verify_comments(type, actual, expected) { - "missing_#{type}s": (expected.keys - actual.keys).map { |it| actual[it]&.slice(:title, :url) }.compact, - "extra_#{type}s": (actual.keys - expected.keys).map { |it| expected[it]&.slice(:title, :url) }.compact, + "missing_#{type}s": (expected.keys - actual.keys).map { |it| expected[it]&.slice(:title, :url) }.compact, + "extra_#{type}s": (actual.keys - expected.keys).map { |it| actual[it]&.slice(:title, :url) }.compact, "#{type}_comments": comment_diff } end @@ -336,11 +296,12 @@ module QA # # @param [QA::Resource::Project] # @param [Runtime::API::Client] client + # @param [Boolean] transform_urls # @return [Hash] - def fetch_mrs(project, client) + def fetch_mrs(project, client, transform_urls: false) imported_mrs = project.merge_requests(auto_paginate: true, attempts: 2) - Parallel.map(imported_mrs, in_threads: 4) do |mr| + Parallel.map(imported_mrs, in_threads: 6) do |mr| resource = Resource::MergeRequest.init do |resource| resource.project = project resource.iid = mr[:iid] @@ -350,11 +311,11 @@ module QA [mr[:iid], { url: mr[:web_url], title: mr[:title], - body: sanitize_description(mr[:description]) || '', + body: sanitize_description(mr[:description], transform_urls) || '', state: mr[:state], comments: resource .comments(auto_paginate: true, attempts: 2) - .map { |c| sanitize_comment(c[:body]) } + .map { |c| sanitize_comment(c[:body], transform_urls) } }] end.to_h end @@ -363,11 +324,12 @@ module QA # # @param [QA::Resource::Project] # @param [Runtime::API::Client] client + # @param [Boolean] transform_urls # @return [Hash] - def fetch_issues(project, client) + def fetch_issues(project, client, transform_urls: false) imported_issues = project.issues(auto_paginate: true, attempts: 2) - Parallel.map(imported_issues, in_threads: 4) do |issue| + Parallel.map(imported_issues, in_threads: 6) do |issue| resource = Resource::Issue.init do |issue_resource| issue_resource.project = project issue_resource.iid = issue[:iid] @@ -378,42 +340,66 @@ module QA url: issue[:web_url], title: issue[:title], state: issue[:state], - body: sanitize_description(issue[:description]) || '', + body: sanitize_description(issue[:description], transform_urls) || '', comments: resource .comments(auto_paginate: true, attempts: 2) - .map { |c| sanitize_comment(c[:body]) } + .map { |c| sanitize_comment(c[:body], transform_urls) } }] end.to_h end - # Importer user mention pattern + # Remove added postfixes and transform urls # - # @return [Regex] - def created_by_pattern - @created_by_pattern ||= /\n\n \*By #{importer_username_pattern} on \S+ \(imported from GitLab\)\*/ + # Source urls need to be replaced with target urls for comparison to work + # + # @param [String] body + # @param [Boolean] transform_urls + # @return [String] + def sanitize_comment(body, transform_urls) + comment = body&.gsub(created_by_pattern, "") + return comment unless transform_urls + + comment&.gsub(source_project_url, imported_project_url) end - # Username of importer user for removal from comments and descriptions + # Remove added postfixes and transform urls + # + # Source urls need to be replaced with target urls for comparison to work # + # @param [String] body + # @param [Boolean] transform_urls # @return [String] - def importer_username_pattern - @importer_username_pattern ||= ENV['QA_LARGE_IMPORT_USER_PATTERN'] || "(gitlab-migration|GitLab QA Bot)" + def sanitize_description(body, transform_urls) + description = body&.gsub(created_by_pattern, "") + return description unless transform_urls + + description&.gsub(source_project_url, imported_project_url) end - # Remove added prefixes from comments + # Following objects are memoized via instance variables due to Parallel having some type of issue calling + # helpers defined via rspec let method + + # Importer user mention pattern + # + # @return [Regex] + def created_by_pattern + @created_by_pattern ||= /\n\n \*By .+ on \S+ \(imported from GitLab\)\*/ + end + + # Source project url # - # @param [String] body # @return [String] - def sanitize_comment(body) - body&.gsub(created_by_pattern, "") + def source_project_url + @source_group_url ||= "#{source_gitlab_address}/#{source_project.full_path}" end - # Remove created by prefix from descripion + # Imported project url + # + # This needs to be constructed manually because it is called before project import finishes # - # @param [String] body # @return [String] - def sanitize_description(body) - body&.gsub(created_by_pattern, "") + def imported_project_url + @imported_group_url ||= "#{Runtime::Scenario.gitlab_address}/#{imported_group.full_path}/#{source_project.path}" end # Save json as file diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb index 07e54ead9c8..7fe11c3bafe 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_members_spec.rb @@ -5,39 +5,37 @@ module QA describe 'Gitlab migration', product_group: :import do include_context 'with gitlab project migration' - let(:member) do + let!(:source_member) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = source_admin_api_client + end.tap(&:set_public_email) + end + + let!(:target_member) do Resource::User.fabricate_via_api! do |usr| usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end + usr.email = source_member.email + end.tap(&:set_public_email) end let(:imported_group_member) do - imported_group.reload!.list_members.find { |usr| usr['username'] == member.username } + imported_group.reload!.list_members.find { |usr| usr['username'] == target_member.username } end let(:imported_project_member) do - imported_project.reload!.list_members.find { |usr| usr['username'] == member.username } - end - - before do - member.set_public_email - end - - after do - member.remove_via_api! + imported_project.reload!.list_members.find { |usr| usr['username'] == target_member.username } end context 'with group member' do before do - source_group.add_member(member, Resource::Members::AccessLevel::DEVELOPER) + source_group.add_member(source_member, Resource::Members::AccessLevel::DEVELOPER) end it( 'member retains indirect membership in imported project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354416' ) do - expect_import_finished + expect_project_import_finished_successfully aggregate_failures do expect(imported_project_member).to be_nil @@ -50,14 +48,14 @@ module QA context 'with project member' do before do - source_project.add_member(member, Resource::Members::AccessLevel::DEVELOPER) + source_project.add_member(source_member, Resource::Members::AccessLevel::DEVELOPER) end it( 'member retains direct membership in imported project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354417' ) do - expect_import_finished + expect_project_import_finished_successfully aggregate_failures do expect(imported_group_member).to be_nil diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb index f44786939dc..127db36052f 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb @@ -7,57 +7,64 @@ module QA let!(:source_project_with_readme) { true } - # We create additional user so that object being migrated is not owned by the user doing migration - let!(:other_user) do - Resource::User - .fabricate_via_api! { |usr| usr.api_client = admin_api_client } - .tap do |usr| - usr.set_public_email - source_project.add_member(usr, Resource::Members::AccessLevel::MAINTAINER) - end + let!(:source_mr_reviewer) do + reviewer = Resource::User.fabricate_via_api! do |usr| + usr.api_client = source_admin_api_client + usr.username = "source-reviewer-#{SecureRandom.hex(6)}" + end + reviewer.tap do |usr| + usr.set_public_email + source_project.add_member(usr, Resource::Members::AccessLevel::MAINTAINER) + end end let!(:source_mr) do Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = source_project - mr.api_client = Runtime::API::Client.new(user: other_user) - mr.reviewer_ids = [other_user.id] + mr.api_client = source_admin_api_client + mr.reviewer_ids = [source_mr_reviewer.id] end end - let!(:source_comment) { source_mr.add_comment(body: 'This is a test comment!') } + let!(:mr_reviewer) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = admin_api_client + usr.email = source_mr_reviewer.email + end.tap(&:set_public_email) + end - let(:imported_mrs) { imported_project.merge_requests } - let(:imported_mr_comments) { imported_mr.comments.map { |note| note.except(:id, :noteable_id) } } - let(:source_mr_comments) { source_mr.comments.map { |note| note.except(:id, :noteable_id) } } + let!(:source_mr_reviewers) { [source_mr_reviewer.email] } + let!(:source_mr_approvers) { [source_admin_user.email] } + let(:source_mr_comments) do + source_mr.comments.map do |note| + { **note.except(:id, :noteable_id), author: note[:author].except(:web_url) } + end + end + let(:imported_mrs) { imported_project.merge_requests } let(:imported_mr) do Resource::MergeRequest.init do |mr| mr.project = imported_project - mr.iid = imported_mrs.first[:iid] + mr.iid = imported_project.merge_requests.first[:iid] mr.api_client = api_client end end - let(:imported_mr_reviewers) { imported_mr.reviewers.map { |r| r.slice(:name, :username) } } - let(:source_mr_reviewers) { [{ name: other_user.name, username: other_user.username }] } + let(:imported_mr_comments) do + imported_mr.comments.map do |note| + { **note.except(:id, :noteable_id), author: note[:author].except(:web_url) } + end + end + let(:imported_mr_reviewers) { imported_mr.reviewers.map { |reviewer| reviewer[:username] } } let(:imported_mr_approvers) do - imported_mr.approval_configuration[:approved_by].map do |usr| - { username: usr.dig(:user, :username), name: usr.dig(:user, :name) } - end + imported_mr.approval_configuration[:approved_by].map { |usr| usr.dig(:user, :username) } end before do - source_project.update_approval_configuration( - merge_requests_author_approval: true, - approvals_before_merge: 1 - ) + source_project.update_approval_configuration(merge_requests_author_approval: true, approvals_before_merge: 1) source_mr.approve - end - - after do - other_user.remove_via_api! + source_mr.add_comment(body: 'This is a test comment!') end context 'with merge request' do @@ -65,15 +72,15 @@ module QA 'successfully imports merge request', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348478' ) do - expect_import_finished + expect_project_import_finished_successfully expect(imported_mrs.count).to eq(1) aggregate_failures do expect(imported_mr).to eq(source_mr.reload!) expect(imported_mr_comments).to match_array(source_mr_comments) - expect(imported_mr_reviewers).to eq(source_mr_reviewers) - expect(imported_mr_approvers).to eq([{ username: other_user.username, name: other_user.name }]) + expect(imported_mr_reviewers).to eq([mr_reviewer.username]) + expect(imported_mr_approvers).to eq([source_admin_user.username]) end end end diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb index 7b79e6967c7..8d631808d17 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb @@ -22,7 +22,7 @@ module QA before do Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.api_client = api_client + commit.api_client = source_admin_api_client commit.project = source_project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files( @@ -47,7 +47,7 @@ module QA 'successfully imports ci pipeline', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354650' ) do - expect_import_finished + expect_project_import_finished_successfully expect(imported_pipelines).to eq(source_pipelines) end diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb index 2b7818d1ed2..83691cdf143 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb @@ -5,14 +5,54 @@ module QA describe 'Gitlab migration', product_group: :import do include_context 'with gitlab project migration' + # this spec is used as a sanity test for gitlab migration because it can run outside of orchestrated setup + context 'with import within same instance', orchestrated: false, import: false, quarantine: { + type: :test_environment, + issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/383605", + only: { job: "review-qa" } + } do + let!(:source_project_with_readme) { true } + let!(:source_gitlab_address) { Runtime::Scenario.gitlab_address } + let!(:source_admin_api_client) { admin_api_client } + + let!(:source_sandbox) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = admin_api_client + end + end + + let!(:target_sandbox) { source_sandbox } + + let!(:source_group) do + Resource::Group.fabricate_via_api! do |group| + group.api_client = admin_api_client + group.sandbox = source_sandbox + group.path = "source-group-for-import-#{SecureRandom.hex(4)}" + group.avatar = File.new('qa/fixtures/designs/tanuki.jpg', 'r') + end + end + + let(:destination_group_path) { "target-group-for-import-#{SecureRandom.hex(4)}" } + let(:cleanup!) { user.remove_via_api! } + + it( + 'successfully imports project', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/383351' + ) do + expect_project_import_finished_successfully + + expect(imported_project).to eq(source_project) + end + end + context 'with uninitialized project' do it( 'successfully imports project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347610' ) do - expect_import_finished + expect_project_import_finished_successfully - expect(imported_projects.first).to eq(source_project) + expect(imported_project).to eq(source_project) end end @@ -59,7 +99,7 @@ module QA 'successfully imports repository', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347570' ) do - expect_import_finished + expect_project_import_finished_successfully aggregate_failures do expect(imported_commits).to match_array(source_commits) @@ -78,9 +118,9 @@ module QA 'successfully imports project wiki', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347567' ) do - expect_import_finished + expect_project_import_finished_successfully - expect(imported_projects.first.wikis).to eq(source_project.wikis) + expect(imported_project.wikis).to eq(source_project.wikis) end end end 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 index 36036a2321e..b3510cef3e9 100644 --- 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 @@ -6,13 +6,13 @@ module QA include_context 'with gitlab project migration' context 'with release' do - let(:tag) { 'v0.0.1' } - let(:source_project_with_readme) { true } + let!(:tag) { 'v0.0.1' } + let!(:source_project_with_readme) { true } - let(:milestone) do + let!(:milestone) do Resource::ProjectMilestone.fabricate_via_api! do |resource| resource.project = source_project - resource.api_client = api_client + resource.api_client = source_admin_api_client end end @@ -60,7 +60,7 @@ module QA 'successfully imports project release', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360243' ) do - expect_import_finished + expect_project_import_finished_successfully expect(imported_releases.size).to eq(1), "Expected to have 1 migrated release" expect(imported_release).to eq(source_release) diff --git a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb index 16d4fd35b69..3df6e988bfa 100644 --- a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb +++ b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb @@ -70,13 +70,7 @@ module QA it( 'is allowed to commit to sub-group project via the API', - :reliable, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349', - quarantine: { - only: { subdomain: %i[staging staging-ref] }, - type: :investigating, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/370282' - } + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349' ) do expect do Resource::Repository::Commit.fabricate_via_api! do |commit| @@ -87,6 +81,9 @@ module QA commit.commit_message = 'Add new file' commit.add_files([{ file_path: 'test.txt', content: 'new file' }]) end + rescue StandardError => e + QA::Runtime::Logger.error("Full failure message: #{e.message}") + raise end.not_to raise_error end diff --git a/qa/qa/specs/features/api/4_verify/file_variable_spec.rb b/qa/qa/specs/features/api/4_verify/file_variable_spec.rb index 4ae97f589cf..6d375341c1b 100644 --- a/qa/qa/specs/features/api/4_verify/file_variable_spec.rb +++ b/qa/qa/specs/features/api/4_verify/file_variable_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring do + RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring, quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/383324', + type: :stale + } do describe 'Pipeline with project file variables' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } diff --git a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb index eb1b085c35c..7aaaa7137ed 100644 --- a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb +++ b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb @@ -17,17 +17,16 @@ module QA # Removing a runner via the UI is covered by `spec/features/runners_spec.rb`` it 'removes the runner', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354828' do - runners = nil - expect { (runners = runner.list_of_runners(tag_list: runner_tags)).size } - .to eventually_eq(1).within(max_duration: 10, sleep_interval: 1) - expect(runners.first[:description]).to eq(executor) + runners_list = runner.runners_list + expect(runners_list.size).to eq(1) + expect(runners_list.first[:description]).to eq(executor) - request = Runtime::API::Request.new(api_client, "runners/#{runners.first[:id]}") + request = Runtime::API::Request.new(api_client, "runners/#{runner.id}") response = delete(request.url) expect(response.code).to eq(Support::API::HTTP_STATUS_NO_CONTENT) expect(response.body).to be_empty - expect(runner.list_of_runners(tag_list: runner_tags)).to be_empty + expect(runner).to be_not_found_by_tags end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb deleted file mode 100644 index c690202f091..00000000000 --- a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -module QA - describe 'Manage', :requires_admin, :reliable, product_group: :import do - describe 'Gitlab migration' do - let!(:admin_api_client) { Runtime::API::Client.as_admin } - let!(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end - end - - let!(:api_client) { Runtime::API::Client.new(user: user) } - let!(:personal_access_token) { api_client.personal_access_token } - - let(:sandbox) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client - end - end - - let(:source_group) do - Resource::Sandbox.fabricate! do |group| - group.api_client = api_client - group.path = "source-group-for-import-#{SecureRandom.hex(4)}" - end - end - - let(:imported_group) do - Resource::BulkImportGroup.init do |group| - group.api_client = api_client - group.sandbox = sandbox - group.source_group = source_group - end - end - - before do - sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - - Flow::Login.sign_in(as: user) - - source_group - - Page::Main::Menu.perform(&:go_to_create_group) - Page::Group::New.perform do |group| - group.switch_to_import_tab - group.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token) - end - end - - after do - user.remove_via_api! - end - - it( - 'imports group from UI', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862', - issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252', - issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678', - issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351', - except: { job: 'instance-image-slow-network' } - ) do - Page::Group::BulkImport.perform do |import_page| - import_page.import_group(imported_group.path, imported_group.sandbox.path) - - expect(import_page).to have_imported_group(imported_group.path, wait: 300) - - imported_group.reload!.visit! - Page::Group::Show.perform do |group| - expect(group).to have_content(imported_group.path) - end - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb new file mode 100644 index 00000000000..8fe4dc192bd --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage' do + describe 'Subgroup transfer', product_group: :workspace do + let(:source_group) do + Resource::Group.fabricate_via_api! do |group| + group.path = "source-group-for-transfer_#{SecureRandom.hex(8)}" + end + end + + let!(:target_group) do + Resource::Group.fabricate_via_api! do |group| + group.path = "target-group-for-transfer_#{SecureRandom.hex(8)}" + end + end + + let(:sub_group_for_transfer) do + Resource::Group.fabricate_via_api! do |group| + group.path = "subgroup-for-transfer_#{SecureRandom.hex(8)}" + group.sandbox = source_group + end + end + + before do + Flow::Login.sign_in + sub_group_for_transfer.visit! + end + + it 'transfers a subgroup to another group', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347692' do + Page::Group::Menu.perform(&:click_group_general_settings_item) + Page::Group::Settings::General.perform do |general| + general.transfer_group(sub_group_for_transfer, target_group) + + sub_group_for_transfer.sandbox = target_group + sub_group_for_transfer.reload! + end + + expect(page).to have_text("Group '#{sub_group_for_transfer.path}' was successfully transferred.") + expect(page.driver.current_url).to include(sub_group_for_transfer.full_path) + 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/import/import_github_repo_spec.rb index 15563e3aa2a..43a8af93e27 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/import/import_github_repo_spec.rb @@ -1,21 +1,11 @@ # frozen_string_literal: true module QA - # Spec uses real github.com, which means outage of github can actually block deployment - # Keep spec in reliable bucket but don't run in blocking pipelines - RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin, product_group: :import do + RSpec.describe 'Manage', product_group: :import do describe 'GitHub import' do - context 'when imported via UI' do - let(:github_repo) { 'gitlab-qa-github/import-test' } - let(:api_client) { Runtime::API::Client.as_admin } - let(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } - let(:user) do - Resource::User.fabricate_via_api! do |resource| - resource.api_client = api_client - resource.hard_delete_on_api_removal = true - end - end + include_context 'with github import' + context 'when imported via UI' do let(:imported_project) do Resource::ProjectImportedFromGithub.init do |project| project.import = true @@ -39,8 +29,6 @@ module QA end before do - group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - Flow::Login.sign_in(as: user) Page::Main::Menu.perform(&:go_to_create_project) Page::Project::New.perform do |project_page| @@ -49,10 +37,6 @@ module QA end end - after do - user.remove_via_api! - end - it 'imports a project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347877' do Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(Runtime::Env.github_access_token) diff --git a/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb new file mode 100644 index 00000000000..4bcd2c44617 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module QA + describe 'Manage', :reliable, product_group: :import do + describe 'Gitlab migration' do + include_context "with gitlab group migration" + + let!(:imported_group) do + Resource::BulkImportGroup.init do |group| + group.api_client = api_client + group.sandbox = target_sandbox + group.source_group = source_group + end + end + + before do + Flow::Login.sign_in(as: user) + + Page::Main::Menu.perform(&:go_to_create_group) + Page::Group::New.perform do |group| + group.switch_to_import_tab + group.connect_gitlab_instance(source_gitlab_address, source_admin_api_client.personal_access_token) + end + end + + it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862' do + Page::Group::BulkImport.perform do |import_page| + import_page.import_group(source_group.path, target_sandbox.path) + + expect(import_page).to have_imported_group(imported_group.path, wait: 300) + + imported_group.reload!.visit! + Page::Group::Show.perform do |group| + expect(group).to have_content(imported_group.path) + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb index 66208921f0e..eaf43f04c4b 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do + RSpec.describe 'Plan', product_group: :product_planning do describe 'Design Management' do let(:issue) { Resource::Issue.fabricate_via_api! } let(:design_filename) { 'banana_sample.gif' } diff --git a/qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb index 8cbc6d7209c..03b2bc6823a 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do + RSpec.describe 'Plan', product_group: :product_planning do describe 'Design Management' do let(:first_design) { Resource::Design.fabricate! } diff --git a/qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb index 8f4902026d2..61b67441ebb 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Plan', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/366839', - type: :test_environment, - only: { job: 'review-qa-*' } - } do + RSpec.describe 'Plan', product_group: :product_planning do describe 'Design Management' do let(:design) do Resource::Design.fabricate_via_browser_ui! do |design| diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index 36cfb9dfb6e..fd818c3797b 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -17,7 +17,7 @@ module QA before do Flow::Login.sign_in - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? # Ensure user exists user Flow::Login.sign_in_as_admin @@ -31,7 +31,7 @@ module QA project.add_member(user) end - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? Resource::Issue.fabricate_via_browser_ui! do |issue| issue.project = project end.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 330cae575e4..236af93716f 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -27,7 +27,7 @@ module QA merge_request.visit! Page::MergeRequest::Show.perform do |mr_page| - expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.') + expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.', wait: 20) expect(mr_page).to be_fast_forward_not_possible expect(mr_page).not_to have_merge_button expect(merge_request.project.commits.size).to eq(2) diff --git a/qa/qa/specs/features/browser_ui/3_create/pages/new_static_page_spec.rb b/qa/qa/specs/features/browser_ui/3_create/pages/new_static_page_spec.rb index c35aa403bfa..449bffe61e0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/pages/new_static_page_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/pages/new_static_page_spec.rb @@ -1,13 +1,21 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :gitlab_pages, :orchestrated, except: { job: 'review-qa-*', subdomain: :production } do + RSpec.describe 'Create', + :gitlab_pages, + :orchestrated, + except: { job: 'review-qa-*' }, + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/383215', + type: :test_environment, + only: { subdomain: 'staging-ref' } + } do # TODO: Convert back to :smoke once proved to be stable. Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300906 describe 'Pages', product_group: :editor do let!(:project) do Resource::Project.fabricate_via_api! do |project| - project.name = 'jekyll-pages-project' - project.template_name = :jekyll + project.name = 'gitlab-pages-project' + project.template_name = :plainhtml end end @@ -45,7 +53,8 @@ module QA pages.go_to_access_page Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page, retry_on_exception: true) do - expect(page).to have_content('Write an awesome description for your new site here.') + expect(page).to have_content( + 'This is a simple plain-HTML website on GitLab Pages, without any fancy static site generator.') end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb index 0503b1b3761..b98bb8592d3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb @@ -2,7 +2,10 @@ module QA RSpec.describe 'Create' do - describe 'Branch with unusual name', product_group: :source_code do + describe 'Branch with unusual name', product_group: :source_code, quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/364565', + type: :bug + } do let(:branch_name) { 'unUsually/named#br--anch' } let(:project) do Resource::Project.fabricate_via_api! do |resource| @@ -29,6 +32,16 @@ module QA Page::Project::Show.perform do |show| show.switch_to_branch(branch_name) + + # It takes a few seconds for console errors to appear + sleep 3 + + errors = page.driver.browser.logs.get(:browser) + .select { |e| e.level == "SEVERE" } + .to_a + + raise("Console error(s):\n#{errors.join("\n\n")}") if errors.present? + show.click_file('test-folder') expect(show).to have_file('test-file.md') diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb index 0e5fcea438d..aeb8e7d27bf 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb @@ -18,7 +18,7 @@ module QA end after do - if QA::Support::FIPS.enabled? + if Runtime::Env.personal_access_tokens_disabled? snippet.visit! Page::Dashboard::Snippet::Show.perform(&:click_delete_button) else diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 8ea65e17e13..93f804f1e39 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Web IDE file templates' do include Runtime::Fixtures @@ -11,6 +11,11 @@ module QA project.description = 'Add file templates via the Web IDE' project.initialize_with_readme = true end + Runtime::Feature.disable(:vscode_web_ide, project: @project) + end + + after(:all) do + Runtime::Feature.enable(:vscode_web_ide, project: @project) end templates = [ @@ -54,6 +59,7 @@ module QA Page::Project::Show.perform(&:open_web_ide!) Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.create_new_file_from_template template[:file_name], template[:name] expect(ide.has_file?(template[:file_name])).to be_truthy diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb index 1da9ed652fe..a001dee891a 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Add a directory in Web IDE' do let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -11,11 +11,15 @@ module QA end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in - project.visit! end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + context 'when a directory with the same name already exists' do let(:directory_name) { 'first_directory' } @@ -38,6 +42,11 @@ module QA it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347733' do Page::Project::WebIDE::Edit.perform do |ide| + # Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page, + # retry_on_exception: true) do + # expect(ide).to have_element(:commit_mode_tab) + # end + ide.wait_until_ide_loads ide.add_directory(directory_name) end @@ -54,6 +63,7 @@ module QA it 'shows in the tree view but cannot be committed', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347732' do Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.add_directory(directory_name) expect(ide).to have_file(directory_name) diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb index 1dfda1608f4..cb0da601a88 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'First file using Web IDE' do let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -13,14 +13,20 @@ module QA let(:file_name) { 'the very first file.txt' } before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + it "creates the first file in an empty project via Web IDE", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347803' do project.visit! Page::Project::Show.perform(&:create_first_new_file!) Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.create_first_file(file_name) ide.commit_changes end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb index 56cf2a08bd9..2007fe4a667 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/link_to_line_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Link to line in Web IDE' do let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } let(:project) do @@ -11,10 +11,12 @@ module QA end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in end after do + Runtime::Feature.enable(:vscode_web_ide, project: project) project.remove_via_api! end @@ -25,6 +27,7 @@ module QA Page::Project::Show.perform(&:open_web_ide_via_shortcut) Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.select_file('app.js') @link = ide.link_line('26') end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb index 820b47a3175..dc9f68c5c73 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Open a fork in Web IDE', skip: { issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/351696", @@ -14,6 +14,14 @@ module QA end end + before do + Runtime::Feature.disable(:vscode_web_ide, project: parent_project) + end + + after do + Runtime::Feature.enable(:vscode_web_ide, project: parent_project) + end + context 'when a user does not have permissions to commit to the project' do let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) } @@ -57,6 +65,7 @@ module QA def submit_merge_request_upstream Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads expect(ide).to have_project_path("#{user.username}/#{parent_project.name}") ide.add_file('new file', 'some random text') diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb index 685cd2d4ad6..039d25477bf 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_web_ide_from_diff_tab_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Open Web IDE from Diff Tab' do files = [ { @@ -44,11 +44,15 @@ module QA end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in - merge_request.visit! end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + it 'opens and edits a multi-file merge request in Web IDE from Diff Tab', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347724' do Page::MergeRequest::Show.perform do |show| show.click_diffs_tab @@ -56,6 +60,7 @@ module QA end Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads files.each do |files| expect(ide).to have_file(files[:file_path]) expect(ide).to have_file_content(files[:file_path], files[:content]) diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb index e4f29952f99..fe0060e9bbc 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb @@ -1,31 +1,44 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/381530', type: :stale } do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Review a merge request in Web IDE' do let(:new_file) { 'awesome_new_file.txt' } let(:original_text) { 'Text' } let(:review_text) { 'Reviewed ' } + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'review-merge-request-spec-project' + project.initialize_with_readme = true + end + end + let(:merge_request) do Resource::MergeRequest.fabricate_via_api! do |mr| mr.file_name = new_file mr.file_content = original_text + mr.project = project end end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in - merge_request.visit! end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + it 'opens and edits a merge request in Web IDE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347786' do Page::MergeRequest::Show.perform do |show| show.click_open_in_web_ide end Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.has_file?(new_file) ide.add_to_modified_content(review_text) ide.commit_changes diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/server_hooks_custom_error_message_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/server_hooks_custom_error_message_spec.rb index 0972e4f3e3d..3cd14ecd799 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/server_hooks_custom_error_message_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/server_hooks_custom_error_message_spec.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', :skip_live_env, except: { job: 'review-qa-*' }, product_group: :editor do + RSpec.describe 'Create', :skip_live_env, except: { job: 'review-qa-*' }, + feature_flag: { name: 'vscode_web_ide', scope: :project }, + product_group: :editor do describe 'Git Server Hooks' do let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', 'README.md')) } @@ -14,15 +16,21 @@ module QA end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in project.visit! end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + context 'Custom error messages' do it 'renders preconfigured error message when user hook failed on commit in WebIDE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/364751' do Page::Project::Show.perform(&:open_web_ide_via_shortcut) Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.upload_file(file_path) ide.commit_changes(wait_for_success: false) expect(ide).to have_text('Custom error message rejecting prereceive hook for projects with GL_PROJECT_PATH') diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb index c0f65416a1c..c6e283f67e0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', product_group: :editor do + RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor do describe 'Upload a file in Web IDE' do let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', file_name)) } @@ -13,17 +13,23 @@ module QA end before do + Runtime::Feature.disable(:vscode_web_ide, project: project) Flow::Login.sign_in project.visit! Page::Project::Show.perform(&:open_web_ide!) end + after do + Runtime::Feature.enable(:vscode_web_ide, project: project) + end + context 'when a file with the same name already exists' do let(:file_name) { 'README.md' } it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347850' do Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.upload_file(file_path) end @@ -36,6 +42,7 @@ module QA it 'shows the Edit tab with the text', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347852' do Page::Project::WebIDE::Edit.perform do |ide| + ide.wait_until_ide_loads ide.upload_file(file_path) expect(ide).to have_file(file_name) diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb index f90676ee15a..695b295bd86 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb @@ -10,10 +10,12 @@ module QA issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338179', type: :bug }, + feature_flag: { name: 'vscode_web_ide', scope: :project }, product_group: :editor ) do describe 'Web IDE web terminal' do before do + Runtime::Feature.disable(:vscode_web_ide, project: project) project = Resource::Project.fabricate_via_api! do |project| project.name = 'web-terminal-project' end @@ -56,6 +58,7 @@ module QA end after do + Runtime::Feature.enable(:vscode_web_ide, project: project) @runner.remove_via_api! if @runner end @@ -76,6 +79,7 @@ module QA # There are also FE specs # * spec/frontend/ide/components/terminal/terminal_controls_spec.js Page::Project::WebIDE::Edit.perform do |edit| + edit.wait_until_ide_loads edit.start_web_terminal expect(edit).to have_no_alert 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 index 4a0a8be3659..5dda8b04805 100644 --- 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 @@ -2,10 +2,7 @@ module QA RSpec.describe 'Verify', :runner do - describe 'Pipeline with customizable variable', feature_flag: { - name: :run_pipeline_graphql, - scope: :project - } 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' } @@ -48,74 +45,45 @@ module QA end end - shared_examples 'pipeline with custom variable' do - before do - Flow::Login.sign_in + before do + Flow::Login.sign_in - project.visit! - Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) + project.visit! + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) - # Sometimes the variables will not be prefilled because of reactive cache so we revisit the page again. - # TODO: Investigate alternatives to deal with cache implementation - # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/381233 - page.refresh - end - - after do - runner&.remove_via_api! - end - - it 'manually creates a pipeline and uses the defined custom variable value' 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! + # Sometimes the variables will not be prefilled because of reactive cache so we revisit the page again. + # TODO: Investigate alternatives to deal with cache implementation + # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/381233 + page.refresh + end - Page::Project::Job::Show.perform do |show| - expect(show.output).to have_content(variable_custom_value) - end - end + after do + runner&.remove_via_api! end - # TODO: Clean up tests when run_pipeline_graphql is enabled - # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/372310 - context( - 'with feature flag disabled', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/361814' - ) do - before do - Runtime::Feature.disable(:run_pipeline_graphql, project: project) + it 'manually creates a pipeline and uses the defined custom variable value', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/378975' do + Page::Project::Pipeline::New.perform do |new| + new.configure_variable(value: variable_custom_value) + new.click_run_pipeline_button end - it_behaves_like 'pipeline with custom variable' - end - - context( - 'with feature flag enabled', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/378975' - ) do - before do - Runtime::Feature.enable(:run_pipeline_graphql, project: project) + Page::Project::Pipeline::Show.perform do |show| + Support::Waiter.wait_until { show.passed? } end - after do - Runtime::Feature.disable(:run_pipeline_graphql, project: project) + 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 - it_behaves_like 'pipeline with custom variable' + job.visit! + + Page::Project::Job::Show.perform do |show| + expect(show.output).to have_content(variable_custom_value) + end end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb index c4ce916d47d..1878292015e 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb @@ -2,13 +2,12 @@ module QA RSpec.describe 'Verify' do - describe 'Pipeline with prefill variables', feature_flag: { - name: :run_pipeline_graphql, - scope: :project - } do + describe 'Pipeline with prefill variables' do let(:prefill_variable_description1) { Faker::Lorem.sentence } let(:prefill_variable_value1) { Faker::Lorem.word } + let(:prefill_variable_value5) { Faker::Lorem.word } let(:prefill_variable_description2) { Faker::Lorem.sentence } + let(:prefill_variable_description5) { Faker::Lorem.sentence } let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'project-with-prefill-variables' @@ -33,7 +32,12 @@ module QA TEST3: value: test 3 value TEST4: test 4 value - + TEST5: + value: "FOO" + options: + - #{prefill_variable_value5} + - "FOO" + description: #{prefill_variable_description5} test: script: echo "$FOO" YAML @@ -43,62 +47,53 @@ module QA end end - shared_examples 'pre-filled variables form' do - before do - Flow::Login.sign_in + before do + Flow::Login.sign_in + project.visit! - project.visit! - # Navigate to Run Pipeline page - Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) + # Navigate to Run Pipeline page + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) - # Sometimes the variables will not be prefilled because of reactive cache so we revisit the page again. - # TODO: Investigate alternatives to deal with cache implementation - # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/381233 - page.refresh - end + # Sometimes the variables will not be prefilled because of reactive cache so we revisit the page again. + # TODO: Investigate alternatives to deal with cache implementation + # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/381233 + page.refresh + end - it 'shows only variables with description as prefill variables on the run pipeline page' do - Page::Project::Pipeline::New.perform do |new| - aggregate_failures do - expect(new).to have_field('Input variable key', with: 'TEST1') - expect(new).to have_field('Input variable value', with: prefill_variable_value1) - expect(new).to have_content(prefill_variable_description1) + it 'shows only variables with description as prefill variables on the run pipeline page', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/378977' do + Page::Project::Pipeline::New.perform do |new| + aggregate_failures do + expect(new).to have_field('Input variable key', with: 'TEST1') + expect(new).to have_field('Input variable value', with: prefill_variable_value1) + expect(new).to have_content(prefill_variable_description1) + + expect(new).to have_field('Input variable key', with: 'TEST2') + expect(new).to have_field('Input variable value', with: '') + expect(new).to have_content(prefill_variable_description2) - expect(new).to have_field('Input variable key', with: 'TEST2') - expect(new).to have_content(prefill_variable_description2) + expect(new).not_to have_field('Input variable key', with: 'TEST3') + expect(new).not_to have_field('Input variable key', with: 'TEST4') - expect(new).not_to have_field('Input variable key', with: 'TEST3') - expect(new).not_to have_field('Input variable key', with: 'TEST4') - end + expect(new).to have_field('Input variable key', with: 'TEST5') + expect(new).to have_content(prefill_variable_description5) end end end - # TODO: Clean up tests when run_pipeline_graphql is enabled - # Issue https://gitlab.com/gitlab-org/gitlab/-/issues/372310 - context( - 'with feature flag disabled', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/371204' - ) do - before do - Runtime::Feature.disable(:run_pipeline_graphql, project: project) - end - - it_behaves_like 'pre-filled variables form' - end + it 'shows dropdown for variables with description, value, and options defined', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/383820' do + Page::Project::Pipeline::New.perform do |new| + aggregate_failures do + expect(new.variable_dropdown).to have_text('FOO') - context 'with feature flag enabled', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/378977' do - before do - Runtime::Feature.enable(:run_pipeline_graphql, project: project) - end + new.click_variable_dropdown - after do - Runtime::Feature.disable(:run_pipeline_graphql, project: project) + expect(new.variable_dropdown_item_with_index(0)).to have_text(prefill_variable_value5) + expect(new.variable_dropdown_item_with_index(1)).to have_text('FOO') + end end - - it_behaves_like 'pre-filled variables form' end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb index fe934e8c60f..a4849d47183 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb @@ -120,7 +120,7 @@ module QA testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/381486', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/381806', - only: { pipeline: %w[staging staging-canary] }, + only: { pipeline: %w[staging staging-canary staging-ref] }, type: :waiting_on } do before do diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb index e876bf3ab8b..448da6d9d87 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring do - context 'When job is configured to only run on merge_request_events' do + context 'when job is configured to only run on merge_request_events' do let(:mr_only_job_name) { 'mr_only_job' } let(:non_mr_only_job_name) { 'non_mr_only_job' } let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } @@ -31,10 +31,12 @@ module QA file_path: '.gitlab-ci.yml', content: <<~YAML #{mr_only_job_name}: + tags: ["#{executor}"] script: echo 'OK' rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' #{non_mr_only_job_name}: + tags: ["#{executor}"] script: echo 'OK' rules: - if: '$CI_PIPELINE_SOURCE != "merge_request_event"' @@ -57,13 +59,17 @@ module QA before do Flow::Login.sign_in - merge_request.visit! - Page::MergeRequest::Show.perform(&:click_pipeline_link) + # TODO: We should remove (wait) revisiting logic when + # https://gitlab.com/gitlab-org/gitlab/-/issues/385332 is resolved + Support::Waiter.wait_until do + merge_request.visit! + Page::MergeRequest::Show.perform(&:click_pipeline_link) + Page::Project::Pipeline::Show.perform(&:has_merge_request_badge_tag?) + end end after do runner.remove_via_api! - project.remove_via_api! end it 'only runs the job configured to run on merge requests', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347662' do diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb index b1ecce297c9..d30d5b43568 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb @@ -33,10 +33,7 @@ module QA runner.remove_via_api! end - context( - 'when policy is allowed', - quarantine: { type: :flaky, issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/369397' } - ) do + context 'when policy is allowed' do let(:allowed_policies) { %w[if-not-present always never] } where do @@ -87,20 +84,19 @@ module QA let(:allowed_policies) { %w[never] } let(:pull_policies) { %w[always] } - let(:message) do - 'ERROR: Preparation failed: the configured PullPolicies ([always])'\ - ' are not allowed by AllowedPullPolicies ([never])' - end + # The sentence seems differ from time to time, + # only checking portions of the sentence that matter + let(:text1) { 'pull_policy ([always])' } + let(:text2) { 'is not one of the allowed_pull_policies ([never])' } it( 'fails job with policy not allowed message', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368853', - quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/371420', type: :stale } + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368853' ) do visit_job - expect(job_log).to have_content(message), - "Expected to find #{message} in #{job_log}, but didn't." + expect(job_log).to include(text1, text2), + "Expected to find contents #{text1} and #{text2} in #{job_log}, but didn't." end end @@ -123,8 +119,7 @@ module QA tempdir.close! - # Give runner some time to pick up new configuration - sleep(30) + runner.restart end def add_ci_file @@ -154,7 +149,7 @@ module QA def visit_job Page::Project::Pipeline::Show.perform do |show| - Support::Waiter.wait_until { show.completed? } + Support::Waiter.wait_until(max_duration: 90) { show.completed? } show.click_job(job_name) 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 aac8893ff2c..cda45efd828 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,9 +1,10 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, :reliable, product_group: :package_registry do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable, product_group: :package_registry do describe 'Maven group level endpoint' do include Runtime::Fixtures + include Support::Helpers::MaskToken include_context 'packages registry qa scenario' let(:group_id) { 'com.gitlab.qa' } @@ -12,6 +13,18 @@ module QA let(:package_version) { '1.3.7' } let(:package_type) { 'maven' } + let(:group_deploy_token) do + Resource::GroupDeployToken.fabricate_via_api! do |deploy_token| + deploy_token.name = 'maven-group-deploy-token' + deploy_token.group = package_project.group + deploy_token.scopes = %w[ + read_repository + read_package_registry + write_package_registry + ] + end + end + context 'via maven' do where do { @@ -37,11 +50,13 @@ module QA let(:token) do case authentication_token_type when :personal_access_token - personal_access_token + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project) + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: client_project) when :ci_job_token - '${env.CI_JOB_TOKEN}' + '${CI_JOB_TOKEN}' when :project_deploy_token - project_deploy_token.token + use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: package_project) + use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: client_project) end end @@ -121,8 +136,9 @@ module QA context 'duplication setting' do before do + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project) + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: client_project) package_project.group.visit! - Page::Group::Menu.perform(&:go_to_package_settings) end @@ -132,16 +148,19 @@ module QA end it 'prevents users from publishing duplicates', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377491' do - create_duplicated_package + create_package(package_project) + show_latest_deploy_job - push_duplicated_package + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 400) + end - client_project.visit! + Page::Project::Job::Show.perform(&:retry!) show_latest_deploy_job Page::Project::Job::Show.perform do |job| - expect(job).not_to be_successful(timeout: 800) + expect(job).not_to be_successful(timeout: 400) end end end @@ -152,52 +171,32 @@ module QA end it 'allows users to publish duplicates', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377492' do - create_duplicated_package - - push_duplicated_package + create_package(package_project) show_latest_deploy_job Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) + expect(job).to be_successful(timeout: 400) end - end - end - def create_duplicated_package - settings_xml_with_pat = ERB.new(read_fixture('package_managers/maven/group', 'settings_with_pat.xml.erb')).result(binding) - pom_xml = ERB.new(read_fixture('package_managers/maven/group/producer', 'pom.xml.erb')).result(binding) - - with_fixtures([ - { - file_path: 'pom.xml', - content: pom_xml - }, - { - file_path: 'settings.xml', - content: settings_xml_with_pat - } - ]) do |dir| - Service::DockerRun::Maven.new(dir).publish! - end - - package_project.visit! + Page::Project::Job::Show.perform(&:retry!) - Page::Project::Menu.perform(&:click_packages_link) + show_latest_deploy_job - Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 400) + end end end - def push_duplicated_package + def create_package(project) Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gitlab_ci_yaml = ERB.new(read_fixture('package_managers/maven/group/producer', 'gitlab_ci.yaml.erb')).result(binding) pom_xml = ERB.new(read_fixture('package_managers/maven/group/producer', 'pom.xml.erb')).result(binding) settings_xml_with_pat = ERB.new(read_fixture('package_managers/maven/group', 'settings_with_pat.xml.erb')).result(binding) - commit.project = client_project + commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files( [ @@ -210,7 +209,7 @@ module QA end def show_latest_deploy_job - client_project.visit! + package_project.visit! Flow::Pipeline.visit_latest_pipeline 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 8e1b0176f35..46c165ed806 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,13 +1,14 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, :reliable, + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable, feature_flag: { name: 'maven_central_request_forwarding', scope: :global } do describe 'Maven project level endpoint', product_group: :package_registry do include Runtime::Fixtures + include Support::Helpers::MaskToken let(:group_id) { 'com.gitlab.qa' } let(:artifact_id) { "maven-#{SecureRandom.hex(8)}" } @@ -92,11 +93,11 @@ module QA let(:token) do case authentication_token_type when :personal_access_token - personal_access_token + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project) when :ci_job_token - '${env.CI_JOB_TOKEN}' + '${CI_JOB_TOKEN}' when :project_deploy_token - project_deploy_token.token + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: package_project) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb index 63ab826e57b..284130fa92b 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb @@ -40,9 +40,6 @@ module QA after do Runtime::Feature.disable(:rubygem_packages, project: project) - runner.remove_via_api! - package.remove_via_api! - project.remove_via_api! end it 'publishes a Ruby gem', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347649' do 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 057b4c15db1..d6446c9725d 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,9 @@ # frozen_string_literal: true module QA - RSpec.describe 'Configure', only: { subdomain: %i[staging staging-canary] }, product_group: :configure do + RSpec.describe 'Configure', + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/381454', type: :flaky }, + only: { subdomain: %i[staging staging-canary] }, product_group: :configure do describe 'Auto DevOps with a Kubernetes Agent' do let!(:app_project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/8_monitor/.gitkeep b/qa/qa/specs/features/browser_ui/8_monitor/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/qa/qa/specs/features/browser_ui/8_monitor/.gitkeep +++ /dev/null diff --git a/qa/qa/specs/features/browser_ui/8_monitor/incident_management/http_endpoint_integration_creates_alert_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/http_endpoint_integration_creates_alert_spec.rb new file mode 100644 index 00000000000..8ea728ca94c --- /dev/null +++ b/qa/qa/specs/features/browser_ui/8_monitor/incident_management/http_endpoint_integration_creates_alert_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Monitor', product_group: :respond do + describe 'Http endpoint integration' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-alerts' + project.description = 'Project for alerts' + end + end + + let(:random_word) { Faker::Lorem.word } + + let(:payload) do + { title: random_word, description: random_word } + end + + before do + Flow::Login.sign_in + project.visit! + Flow::AlertSettings.setup_http_endpoint_and_send_alert(payload: payload) + end + + it( + 'can send test alert that creates new alert', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/382803' + ) do + Page::Project::Menu.perform(&:go_to_monitor_alerts) + Page::Project::Monitor::Alerts::Index.perform do |alerts| + expect(alerts).to have_alert_with_title(random_word) + end + end + end + end +end diff --git a/qa/qa/specs/features/sanity/interception_spec.rb b/qa/qa/specs/features/sanity/interception_spec.rb new file mode 100644 index 00000000000..f8930db3aa5 --- /dev/null +++ b/qa/qa/specs/features/sanity/interception_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Browser request interception', :orchestrated, :framework do + before(:context) do + skip 'Only can test for chrome' unless QA::Runtime::Env.can_intercept? + end + + before do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + end + + let(:page) { Capybara.current_session } + let(:logger) { class_double('QA::Runtime::Logger') } + + it 'intercepts failed graphql calls' do + page.execute_script <<~JS + fetch('/api/graphql', { + method: 'POST', + body: JSON.stringify({ query: 'query {}'}), + headers: { 'Content-Type': 'application/json' } + }) + JS + + Support::Waiter.wait_until do + !get_cached_error.nil? + end + expect(**get_cached_error).to include({ 'method' => 'POST', 'status' => 200, 'url' => '/api/graphql' }) + end + + def get_cached_error + cache = page.execute_script <<~JS + return Interceptor.getCache() + JS + + cache['errors']&.first + end + end +end diff --git a/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb index 0a0c2a4a6df..27d94b04cde 100644 --- a/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module QA - RSpec.shared_context "with github import", :github, :skip_live_env, :requires_admin, quarantine: { - type: :broken, - issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/382166" - } do + RSpec.shared_context "with github import", :github, :import, :requires_admin, :orchestrated do + include QA::Support::Data::Github + + let!(:github_repo) { "#{github_username}/import-test" } let!(:api_client) { Runtime::API::Client.as_admin } let!(:group) do @@ -28,7 +28,7 @@ module QA project.name = 'imported-project' project.group = group project.github_personal_access_token = Runtime::Env.github_access_token - project.github_repository_path = 'gitlab-qa-github/import-test' + project.github_repository_path = github_repo project.api_client = user_api_client project.issue_events_import = true project.full_notes_import = true @@ -38,9 +38,5 @@ module QA before do group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) end - - after do - user.remove_via_api! - end end end diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb new file mode 100644 index 00000000000..e1d762f41cb --- /dev/null +++ b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module QA + RSpec.shared_context( + 'with gitlab group migration', + :import, + :orchestrated, + requires_admin: 'creates a user via API' + ) do + let!(:import_wait_duration) { { max_duration: 120, sleep_interval: 2 } } + + # source instance objects + # + let!(:source_gitlab_address) { ENV["QA_IMPORT_SOURCE_URL"] || raise("QA_IMPORT_SOURCE_URL is required!") } + let!(:source_admin_api_client) do + Runtime::API::Client.new( + source_gitlab_address, + personal_access_token: Runtime::Env.admin_personal_access_token || raise("Admin access token missing!"), + is_new_session: false + ) + end + let!(:source_admin_user) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = source_admin_api_client + usr.username = Runtime::Env.admin_username || "root" + end.tap(&:set_public_email) + end + let!(:source_group) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = source_admin_api_client + group.path = "source-group-for-import-#{SecureRandom.hex(4)}" + group.avatar = File.new("qa/fixtures/designs/tanuki.jpg", "r") + end + end + + # target instance objects + # + let!(:admin_api_client) { Runtime::API::Client.as_admin } + let!(:admin_user) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = admin_api_client + usr.username = Runtime::Env.admin_username || "root" + end.tap(&:set_public_email) + end + let!(:user) do + Resource::User.fabricate_via_api! do |usr| + usr.api_client = admin_api_client + usr.username = "target-user-#{SecureRandom.hex(6)}" + end + end + let!(:api_client) { Runtime::API::Client.new(user: user) } + let!(:target_sandbox) do + Resource::Sandbox.fabricate_via_api! do |group| + group.api_client = admin_api_client + end + end + + let(:destination_group_path) { source_group.path } + let(:imported_group) do + Resource::BulkImportGroup.fabricate_via_api! do |group| + group.api_client = api_client + group.sandbox = target_sandbox + group.source_group = source_group + group.source_gitlab_address = source_gitlab_address + group.destination_group_path = destination_group_path + group.import_access_token = source_admin_api_client.personal_access_token + end + end + + let(:import_failures) do + imported_group.import_details.sum([]) { |details| details[:failures] } + end + + let(:cleanup!) {} + + def expect_group_import_finished_successfully + imported_group # trigger import + + status = nil + Support::Retrier.retry_until(**import_wait_duration, raise_on_failure: false) do + status = imported_group.import_status + %w[finished failed].include?(status) + end + + # finished status means success, all other statuses are considered to fail the test + expect(status).to eq('finished'), "Expected import to finish successfully, but status was: #{status}" + end + + before do + Runtime::ApplicationSettings.set_application_settings(bulk_import_enabled: true) + + target_sandbox.add_member(user, Resource::Members::AccessLevel::OWNER) + end + + after do |example| + # Checking for failures in the test currently makes test very flaky due to catching unrelated failures + # Log failures for easier debugging + Runtime::Logger.error("Import failures: #{import_failures}") if example.exception && !import_failures.empty? + rescue StandardError + # rescue when import did not happen at all and checking import failues will raise an error + ensure + # make sure cleanup runs last + cleanup! + end + end +end diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb index 9c80c088917..728907c708f 100644 --- a/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb +++ b/qa/qa/specs/features/shared_contexts/import/gitlab_project_migration_common.rb @@ -1,91 +1,32 @@ # frozen_string_literal: true module QA - RSpec.shared_context 'with gitlab project migration', requires_admin: 'creates a user via API', - quarantine: { - type: :flaky, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/364839' - }, - feature_flag: { - name: 'bulk_import_projects', - scope: :global - } do - let(:source_project_with_readme) { false } - let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } } - let(:admin_api_client) { Runtime::API::Client.as_admin } - let(:user) do - Resource::User.fabricate_via_api! do |usr| - usr.api_client = admin_api_client - usr.hard_delete_on_api_removal = true - end - end + RSpec.shared_context 'with gitlab project migration' do + # gitlab project migration doesn't work on just the projects + # so all project migration tests will always require setup for gitlab group migration + include_context "with gitlab group migration" - let(:api_client) { Runtime::API::Client.new(user: user) } - - let(:sandbox) do - Resource::Sandbox.fabricate_via_api! do |group| - group.api_client = admin_api_client - end - end - - let(:destination_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = sandbox - group.path = "destination-group-for-import-#{SecureRandom.hex(4)}" - end - end - - let(:source_group) do - Resource::Group.fabricate_via_api! do |group| - group.api_client = api_client - group.path = "source-group-for-import-#{SecureRandom.hex(4)}" - end - end + let(:source_project_with_readme) { false } let(:source_project) do Resource::Project.fabricate_via_api! do |project| - project.api_client = api_client + project.api_client = source_admin_api_client project.group = source_group project.initialize_with_readme = source_project_with_readme end end - let(:imported_group) do - Resource::BulkImportGroup.fabricate_via_api! do |group| - group.api_client = api_client - group.sandbox = destination_group - group.source_group = source_group - end - end - let(:imported_projects) { imported_group.reload!.projects } let(:imported_project) { imported_projects.first } - let(:import_failures) do - imported_group.import_details.sum([]) { |details| details[:failures] } - end - - def expect_import_finished - imported_group # trigger import - - expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration) + def expect_project_import_finished_successfully + expect_group_import_finished_successfully expect(imported_projects.count).to eq(1), "Expected to have 1 imported project. Found: #{imported_projects.count}" end before do - Runtime::Feature.enable(:bulk_import_projects) - - sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + Runtime::Feature.enable(:bulk_import_projects) unless Runtime::Feature.enabled?(:bulk_import_projects) source_project # fabricate source group and project end - - after do |example| - # Checking for failures in the test currently makes test very flaky due to catching unrelated failures - # Log failures for easier debugging - Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty? - ensure - user.remove_via_api! - end end end diff --git a/qa/qa/specs/helpers/feature_flag.rb b/qa/qa/specs/helpers/feature_flag.rb index 7e618f19ed5..2b0f9e67a41 100644 --- a/qa/qa/specs/helpers/feature_flag.rb +++ b/qa/qa/specs/helpers/feature_flag.rb @@ -20,11 +20,11 @@ module QA # This is to avoid flakiness with other tests running in parallel on the same environment # as well as interfering with feature flag experimentation done by development groups. example.metadata[:skip] = global_feature_flag_message if ContextSelector.dot_com? - else + elsif skip_env_for_scoped_feature_flag # Tests using a feature flag scoped to an actor (ex: :project, :user, :group), or # with no scope defined (such as in the case of a low risk global feature flag), # will only be skipped on environments without an admin account - example.metadata[:skip] = feature_flag_message if skip_env_for_scoped_feature_flag + example.metadata[:skip] = feature_flag_message end end end diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb index a4721040683..003e8aa74c1 100644 --- a/qa/qa/specs/spec_helper.rb +++ b/qa/qa/specs/spec_helper.rb @@ -52,12 +52,10 @@ RSpec.configure do |config| end config.prepend_after do |example| - if example.exception - page = Capybara.page + page = Capybara.page + QA::Support::PageErrorChecker.log_request_errors(page) - QA::Support::PageErrorChecker.log_request_errors(page) - QA::Support::PageErrorChecker.check_page_for_error_code(page) - end + QA::Support::PageErrorChecker.check_page_for_error_code(page) if example.exception end # Add fabrication time to spec metadata diff --git a/qa/qa/support/data/github.rb b/qa/qa/support/data/github.rb new file mode 100644 index 00000000000..b0fe1baeff8 --- /dev/null +++ b/qa/qa/support/data/github.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module QA + module Support + module Data + module Github + def github_username + 'gitlab-qa-github' + end + end + end + end +end + +QA::Support::Data::Github.prepend_mod_with('Support::Data::Github', namespace: QA) diff --git a/qa/qa/support/data/license.rb b/qa/qa/support/data/license.rb new file mode 100644 index 00000000000..cd4745fefcd --- /dev/null +++ b/qa/qa/support/data/license.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Support + module Data + module License + def license_user + 'GitLab QA' + end + + def license_company + 'QA User' + end + + def license_user_count + 10_000 + end + + def license_plan + QA::ULTIMATE_SELF_MANAGED + end + end + end + end +end + +QA::Support::Data::License.prepend_mod_with('Support::Data::License', namespace: QA) diff --git a/qa/qa/support/formatters/allure_metadata_formatter.rb b/qa/qa/support/formatters/allure_metadata_formatter.rb index 02719536b17..c8ddbeb4536 100644 --- a/qa/qa/support/formatters/allure_metadata_formatter.rb +++ b/qa/qa/support/formatters/allure_metadata_formatter.rb @@ -116,8 +116,7 @@ module QA # @return [Array] 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 } + records = data.records runs = records.count failed = records.count { |r| r.values["status"] == "failed" } @@ -136,14 +135,14 @@ module QA def influx_data return [] unless run_type - query_api.query(query: <<~QUERY).values - from(bucket: "#{Support::InfluxdbTools::INFLUX_TEST_METRICS_BUCKET}") - |> range(start: -14d) + query_api.query(query: <<~QUERY) + from(bucket: "#{Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET}") + |> range(start: -30d) |> filter(fn: (r) => r._measurement == "test-stats") |> filter(fn: (r) => r.run_type == "#{run_type}" and r.status != "pending" and r.quarantined == "false" and - r._field == "merge_request_iid" + r._field == "id" ) |> group(columns: ["testcase"]) QUERY diff --git a/qa/qa/support/formatters/test_metrics_formatter.rb b/qa/qa/support/formatters/test_metrics_formatter.rb index e84373a487d..6e6cdc35af5 100644 --- a/qa/qa/support/formatters/test_metrics_formatter.rb +++ b/qa/qa/support/formatters/test_metrics_formatter.rb @@ -8,6 +8,8 @@ module QA class TestMetricsFormatter < RSpec::Core::Formatters::BaseFormatter include Support::InfluxdbTools + CUSTOM_METRICS_KEY = :custom_test_metrics + RSpec::Core::Formatters.register(self, :stop) # Finish test execution @@ -19,16 +21,15 @@ module QA parse_execution_data(notification.examples) - if Runtime::Env.export_metrics? - push_test_metrics - push_fabrication_metrics - end - - save_test_metrics if Runtime::Env.save_metrics_json? + push_test_metrics + push_fabrication_metrics + save_test_metrics end private + delegate :export_metrics?, :save_metrics_json?, :ci_job_url, :ci_job_name, to: "QA::Runtime::Env" + # Save execution data for the run # # @param [Array<RSpec::Core::Example>] examples @@ -42,6 +43,8 @@ module QA # # @return [void] def push_test_metrics + return log(:debug, "Metrics export not enabled, skipping test metrics export") unless export_metrics? + write_api.write(data: execution_data) log(:debug, "Pushed #{execution_data.length} test execution entries to influxdb") rescue StandardError => e @@ -52,6 +55,8 @@ module QA # # @return [void] def push_fabrication_metrics + return log(:debug, "Metrics export not enabled, skipping fabrication metrics export") unless export_metrics? + data = Tools::TestResourceDataProcessor.resources.flat_map do |resource, values| values.map { |v| fabrication_stats(resource: resource, **v) } end @@ -67,6 +72,8 @@ module QA # # @return [void] def save_test_metrics + return log(:debug, "Saving test metrics json not enabled, skipping") unless save_metrics_json? + File.write("tmp/test-metrics-#{env('CI_JOB_NAME_SLUG') || 'local'}.json", execution_data.to_json) rescue StandardError => e log(:error, "Failed to save test execution metrics, error: #{e}") @@ -101,7 +108,8 @@ module QA run_type: run_type, stage: devops_stage(file_path), product_group: example.metadata[:product_group], - testcase: example.metadata[:testcase] + testcase: example.metadata[:testcase], + **custom_metrics_tags(example.metadata) }, fields: { id: example.id, @@ -110,11 +118,12 @@ module QA ui_fabrication: ui_fabrication, total_fabrication: api_fabrication + ui_fabrication, retry_attempts: retry_attempts(example.metadata), - job_url: QA::Runtime::Env.ci_job_url, + job_url: ci_job_url, pipeline_url: env('CI_PIPELINE_URL'), pipeline_id: env('CI_PIPELINE_ID'), job_id: env('CI_JOB_ID'), - merge_request_iid: merge_request_iid + merge_request_iid: merge_request_iid, + **custom_metrics_fields(example.metadata) } } rescue StandardError => e @@ -139,13 +148,13 @@ module QA resource: resource, fabrication_method: fabrication_method, http_method: http_method, - run_type: env('QA_RUN_TYPE') || run_type, + run_type: run_type, merge_request: merge_request }, fields: { fabrication_time: fabrication_time, info: info, - job_url: QA::Runtime::Env.ci_job_url, + job_url: ci_job_url, timestamp: timestamp } } @@ -155,7 +164,7 @@ module QA # # @return [String] def job_name - @job_name ||= QA::Runtime::Env.ci_job_name&.gsub(%r{ \d{1,2}/\d{1,2}}, '') + @job_name ||= ci_job_name&.gsub(%r{ \d{1,2}/\d{1,2}}, '') end # Single common timestamp for all exported example metrics to keep data points consistently grouped @@ -220,6 +229,40 @@ module QA metadata[:retry_attempts] || 0 end + # Additional custom metrics tags + # + # @param [Hash] metadata + # @return [Hash] + def custom_metrics_tags(metadata) + custom_metrics(metadata, :tags) + end + + # Additional custom metrics fields + # + # @param [Hash] metadata + # @return [Hash] + def custom_metrics_fields(metadata) + custom_metrics(metadata, :fields) + end + + # Custom test metrics + # + # @param [Hash] metadata + # @param [Symbol] type type of metric, :fields or :tags + # @return [Hash] + def custom_metrics(metadata, type) + custom_metrics = metadata[CUSTOM_METRICS_KEY] + return {} unless custom_metrics + return {} unless custom_metrics.is_a?(Hash) && custom_metrics[type].is_a?(Hash) + + custom_metrics[type].to_h do |key, value| + k = key.to_sym + v = value.is_a?(Numeric) || value.nil? ? value : value.to_s + + [k, v] + end + end + # Print log message # # @param [Symbol] level diff --git a/qa/qa/support/helpers/mask_token.rb b/qa/qa/support/helpers/mask_token.rb index 0c0af524c97..3aea77779ad 100644 --- a/qa/qa/support/helpers/mask_token.rb +++ b/qa/qa/support/helpers/mask_token.rb @@ -9,9 +9,8 @@ module QA ci_variable.project = project ci_variable.key = name ci_variable.value = value - ci_variable.protected = true end - "$#{name}" + "${#{name}}" end def use_group_ci_variable(name:, value:, group:) @@ -19,9 +18,8 @@ module QA ci_variable.group = group ci_variable.key = name ci_variable.value = value - ci_variable.protected = true end - "$#{name}" + "${#{name}}" end end end diff --git a/qa/qa/support/influxdb_tools.rb b/qa/qa/support/influxdb_tools.rb index e817b096864..efdbe1cd129 100644 --- a/qa/qa/support/influxdb_tools.rb +++ b/qa/qa/support/influxdb_tools.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/module/delegation" - module QA module Support # Common tools for use with influxdb metrics setup diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb index 659b8f10e0a..27d8b144f3b 100644 --- a/qa/qa/support/knapsack_report.rb +++ b/qa/qa/support/knapsack_report.rb @@ -5,13 +5,13 @@ require "fog/google" module QA module Support class KnapsackReport - extend SingleForwardable - PROJECT = "gitlab-qa-resources" BUCKET = "knapsack-reports" FALLBACK_REPORT = "knapsack/master_report.json" - def_delegators :new, :configure!, :move_regenerated_report, :download_report, :upload_report + class << self + delegate :configure!, :move_regenerated_report, :download_report, :upload_report, to: :new + end def initialize(report_name = nil) @report_name = report_name diff --git a/qa/qa/support/loglinking.rb b/qa/qa/support/loglinking.rb index f24577ff313..5a1aad3c7eb 100644 --- a/qa/qa/support/loglinking.rb +++ b/qa/qa/support/loglinking.rb @@ -1,4 +1,7 @@ # frozen_string_literal: true + +require 'active_support/core_ext/integer/time' + module QA module Support module Loglinking @@ -7,63 +10,90 @@ module QA STAGING_REF_ADDRESS = 'https://staging-ref.gitlab.com' PRODUCTION_ADDRESS = 'https://gitlab.com' PRE_PROD_ADDRESS = 'https://pre.gitlab.com' - SENTRY_ENVIRONMENTS = { + SENTRY_BASE_URLS = { staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg', staging_ref: 'https://sentry.gitlab.net/gitlab/staging-ref/?environment=all', pre: 'https://sentry.gitlab.net/gitlab/pregitlabcom/?environment=all', production: 'https://sentry.gitlab.net/gitlab/gitlabcom/?environment=gprd' }.freeze - KIBANA_ENVIRONMENTS = { + KIBANA_BASE_URLS = { staging: 'https://nonprod-log.gitlab.net/', - canary: 'https://log.gprd.gitlab.net/', - production: 'https://log.gprd.gitlab.net/' + production: 'https://log.gprd.gitlab.net/', + pre: 'https://nonprod-log.gitlab.net/' + }.freeze + KIBANA_INDICES = { + staging: 'ed942d00-5186-11ea-ad8a-f3610a492295', + production: '7092c4e2-4eb5-46f2-8305-a7da2edad090', + pre: 'pubsub-rails-inf-pre' }.freeze - def self.failure_metadata(correlation_id) - return if correlation_id.blank? + class << self + def failure_metadata(correlation_id) + return if correlation_id.blank? - sentry_uri = sentry_url - kibana_uri = kibana_url + errors = ["Correlation Id: #{correlation_id}"] - errors = ["Correlation Id: #{correlation_id}"] - errors << "Sentry Url: #{sentry_uri}&query=correlation_id%3A%22#{correlation_id}%22" if sentry_uri - errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20#{correlation_id}%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29" if kibana_uri + env = get_logging_environment - errors.join("\n") - end + unless env.nil? + sentry_base_url = get_sentry_base_url(env) + kibana_base_url = get_kibana_base_url(env) + kibana_index = get_kibana_index(env) - def self.sentry_url - return unless logging_environment? + errors << "Sentry Url: #{get_sentry_url(sentry_base_url, correlation_id)}" if sentry_base_url + errors << "Kibana Url: #{get_kibana_url(kibana_base_url, kibana_index, correlation_id)}" if kibana_base_url + end - SENTRY_ENVIRONMENTS[logging_environment] - end + errors.join("\n") + end - def self.kibana_url - return unless logging_environment? + def get_sentry_base_url(env) + SENTRY_BASE_URLS[env] + end - KIBANA_ENVIRONMENTS[logging_environment] - end + def get_sentry_url(base_url, correlation_id) + "#{base_url}&query=correlation_id%3A%22#{correlation_id}%22" + end - def self.logging_environment - address = QA::Runtime::Scenario.attributes[:gitlab_address] - return if address.nil? - - case address - when STAGING_ADDRESS - :staging - when STAGING_REF_ADDRESS - :staging_ref - when PRODUCTION_ADDRESS - :production - when PRE_PROD_ADDRESS - :pre - else - nil + def get_kibana_base_url(env) + KIBANA_BASE_URLS[env] end - end - def self.logging_environment? - !logging_environment.nil? + def get_kibana_index(env) + KIBANA_INDICES[env] + end + + def get_kibana_url(base_url, index, correlation_id) + "#{base_url}app/discover#/?_a=%28index:%27#{index}%27%2Cquery%3A%28language%3Akuery%2C" \ + "query%3A%27json.correlation_id%20%3A%20#{correlation_id}%27%29%29" \ + "&_g=%28time%3A%28from%3A%27#{start_time}%27%2Cto%3A%27#{end_time}%27%29%29" + end + + def get_logging_environment + address = QA::Runtime::Scenario.attributes[:gitlab_address] + return if address.nil? + + case address + when STAGING_ADDRESS + :staging + when STAGING_REF_ADDRESS + :staging_ref + when PRODUCTION_ADDRESS + :production + when PRE_PROD_ADDRESS + :pre + else + nil + end + end + + def start_time + (Time.now.utc - 24.hours).iso8601(3) + end + + def end_time + Time.now.utc.iso8601(3) + end end end end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index 79ea4a8d001..2e97325aff0 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -79,9 +79,12 @@ module QA super end + # @param name [Symbol] name of the data_qa_selector element + # @param page [Class] a target page class to check existence of (class must inherit from QA::Page::Base) + # @param kwargs [Hash] keyword arguments to pass to Capybara finder def click_element(name, page = nil, **kwargs) msg = ["clicking :#{highlight_element(name)}"] - msg << ", expecting to be at #{page.class}" if page + msg << "and ensuring #{page} is present" if page log(msg.join(' '), :info) log("with args #{kwargs}") diff --git a/qa/qa/support/page_error_checker.rb b/qa/qa/support/page_error_checker.rb index acba25643ae..1d791a83037 100644 --- a/qa/qa/support/page_error_checker.rb +++ b/qa/qa/support/page_error_checker.rb @@ -88,8 +88,8 @@ module QA grouped_errors = group_errors(cache['errors']) - errors = grouped_errors.map do |error_metadata, request_id_string| - "#{error_metadata} -- #{request_id_string}" + errors = grouped_errors.map do |error_metadata, error_body| + "#{error_metadata} -- #{error_body[:request_id_string]}\n#{error_body[:error_body]}" end QA::Runtime::Logger.error "Interceptor Api Errors\n#{errors.join("\n")}" unless errors.nil? || errors.empty? @@ -107,7 +107,7 @@ module QA end def logs(page) - page.driver.browser.manage.logs.get(:browser) + page.driver.browser.logs.get(:browser) end private @@ -120,7 +120,11 @@ module QA errors.each_with_object({}) do |error, memo| url = error['url']&.split('?')&.first || 'Unknown url' key = "[#{error['status']}] #{error['method']} #{url}" - memo[key] = "Correlation Id: #{error.dig('headers', 'x-request-id') || 'Correlation Id not found'}" + request_id_string = "Correlation Id: #{error.dig('headers', 'x-request-id') || 'Correlation Id not found'}" + memo[key] = { + request_id_string: request_id_string, + error_body: error['errorData'] + } end end end diff --git a/qa/qa/support/run.rb b/qa/qa/support/run.rb index 242293f9eef..da82c09462d 100644 --- a/qa/qa/support/run.rb +++ b/qa/qa/support/run.rb @@ -13,7 +13,7 @@ module QA alias_method :to_s, :response def success? - exitstatus == 0 && !response.include?('Error encountered') + exitstatus == 0 && !response.include?('Error encountered') # rubocop:disable Rails/NegateInclude end def to_i @@ -45,3 +45,5 @@ module QA end end end + +QA::Support::Run.prepend_mod_with("Support::Run", namespace: QA) diff --git a/qa/qa/support/ssh.rb b/qa/qa/support/ssh.rb index 1b53244d1e4..eebe5e65504 100644 --- a/qa/qa/support/ssh.rb +++ b/qa/qa/support/ssh.rb @@ -70,3 +70,5 @@ module QA end end end + +QA::Support::SSH.prepend_mod_with("Support::SSH", namespace: QA) diff --git a/qa/qa/tools/long_running_spec_reporter.rb b/qa/qa/tools/long_running_spec_reporter.rb index ce035248baa..865b16f1d41 100644 --- a/qa/qa/tools/long_running_spec_reporter.rb +++ b/qa/qa/tools/long_running_spec_reporter.rb @@ -6,15 +6,15 @@ require "slack-notifier" module QA module Tools class LongRunningSpecReporter - extend SingleForwardable - SLACK_CHANNEL = "#quality-reports" PROJECT = "gitlab-qa-resources" BUCKET = "knapsack-reports" REPORT_NAME = "ee-instance-parallel.json" RUNTIME_THRESHOLD = 300 - def_delegator :new, :execute + class << self + delegate :execute, to: :new + end # Find and report specs exceeding runtime threshold # diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb index b3df6de3d54..fd39b637f83 100644 --- a/qa/qa/tools/reliable_report.rb +++ b/qa/qa/tools/reliable_report.rb @@ -326,7 +326,7 @@ module QA def test_runs(reliable:) puts("Fetching data on #{reliable ? 'reliable ' : ''}test execution for past #{range} days\n".colorize(:green)) - all_runs = query_api.query(query: query(reliable)).values + all_runs = query_api.query(query: query(reliable)) all_runs.each_with_object(Hash.new { |hsh, key| hsh[key] = {} }) do |table, result| records = table.records.sort_by { |record| record.values["_time"] } # skip specs that executed less time than defined by range or stopped executing before report date @@ -341,7 +341,7 @@ module QA runs = records.count failed = records.count { |r| r.values["status"] == "failed" } - failure_rate = (failed.to_f / runs.to_f) * 100 + failure_rate = (failed.to_f / runs) * 100 result[stage][name] = { file: file, @@ -358,7 +358,7 @@ module QA # @return [String] def query(reliable) <<~QUERY - from(bucket: "#{Support::InfluxdbTools::INFLUX_TEST_METRICS_BUCKET}") + from(bucket: "#{Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET}") |> range(start: -#{range}d) |> filter(fn: (r) => r._measurement == "test-stats") |> filter(fn: (r) => r.run_type == "staging-full" or diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb index 068fe37a37b..2aa8845605e 100644 --- a/qa/qa/tools/test_resources_handler.rb +++ b/qa/qa/tools/test_resources_handler.rb @@ -60,7 +60,9 @@ module QA end delete_resources(filtered_resources) - delete_groups_permanently(filtered_resources['QA::Resource::Group']) + + filtered_groups = filtered_resources['QA::Resource::Group'] + delete_groups_permanently(filtered_groups) unless filtered_groups.nil? end return puts "\nDone" if failures.empty? diff --git a/qa/qa/vendor/smocker/event_payload.rb b/qa/qa/vendor/smocker/event_payload.rb index 4bf154b76c2..e7287c741ce 100644 --- a/qa/qa/vendor/smocker/event_payload.rb +++ b/qa/qa/vendor/smocker/event_payload.rb @@ -16,6 +16,10 @@ module QA raw[:object_kind]&.to_sym end + def event_name + raw[:event_name]&.to_sym + end + def project_name raw.dig(:project, :name) end @@ -43,6 +47,14 @@ module QA def wiki? event == :wiki_page end + + def subgroup_create? + event_name == :subgroup_create + end + + def subgroup_destroy? + event_name == :subgroup_destroy + end end end end diff --git a/qa/qa/vendor/smocker/smocker_api.rb b/qa/qa/vendor/smocker/smocker_api.rb index 3f595b58886..230656776b7 100644 --- a/qa/qa/vendor/smocker/smocker_api.rb +++ b/qa/qa/vendor/smocker/smocker_api.rb @@ -31,6 +31,7 @@ module QA def self.teardown! @container&.remove! + @container = nil end def initialize(container, **wait_args) diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index 93a08108787..1a82cda2585 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -5,6 +5,7 @@ require 'capybara/dsl' RSpec.describe QA::Support::Page::Logging do let(:page) { double.as_null_object } let(:logger) { Gitlab::QA::TestLogger.logger(level: ::Logger::DEBUG, source: 'QA Tests') } + let(:page_class) { class_double('QA::Page::TestPage') } before do allow(QA::Runtime::Logger).to receive(:logger).and_return(logger) @@ -66,6 +67,14 @@ RSpec.describe QA::Support::Page::Logging do .to output(/clicking :element/).to_stdout_from_any_process end + it 'logs click_element with a page' do + allow(page_class).to receive(:validate_elements_present!).and_return(true) + allow(page_class).to receive(:to_s).and_return('QA::Page::TestPage') + + expect { subject.click_element(:element, page_class) } + .to output(/clicking :element and ensuring QA::Page::TestPage is present/).to_stdout_from_any_process + end + it 'logs fill_element' do expect { subject.fill_element(:element, 'foo') } .to output(/filling :element with "foo"/).to_stdout_from_any_process diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb index 4cb6ef3c9b5..76cc8e0e303 100644 --- a/qa/spec/resource/api_fabricator_spec.rb +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -113,7 +113,6 @@ RSpec.describe QA::Resource::ApiFabricator do it 'raises a ResourceFabricationFailedError exception' do expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil) expect { subject.fabricate_via_api! }.to raise_error do |error| expect(error.class).to eql(described_class::ResourceFabricationFailedError) @@ -126,7 +125,7 @@ RSpec.describe QA::Resource::ApiFabricator do it 'logs a correlation id' do response = double('Raw POST response', code: 400, body: post_response.to_json, headers: { x_request_id: 'foobar' }) - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil) + allow(QA::Support::Loglinking).to receive(:get_logging_environment).and_return(nil) expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(response) @@ -140,12 +139,14 @@ RSpec.describe QA::Resource::ApiFabricator do end end - it 'logs a sentry url from staging' do + it 'logs Sentry and Kibana URLs from staging' do response = double('Raw POST response', code: 400, body: post_response.to_json, headers: { x_request_id: 'foobar' }) cookies = [{ name: 'Foo', value: 'Bar' }, { name: 'gitlab_canary', value: 'true' }] + time = Time.new(2022, 11, 14, 0, 0, 0, '+00:00') allow(Capybara.current_session).to receive_message_chain(:driver, :browser, :manage, :all_cookies).and_return(cookies) allow(QA::Runtime::Scenario).to receive(:attributes).and_return({ gitlab_address: 'https://staging.gitlab.com' }) + allow(Time).to receive(:now).and_return(time) expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(response) @@ -156,7 +157,7 @@ RSpec.describe QA::Resource::ApiFabricator do Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`. Correlation Id: foobar Sentry Url: https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg&query=correlation_id%3A%22foobar%22 - Kibana Url: https://nonprod-log.gitlab.net/app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foobar%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29 + Kibana Url: https://nonprod-log.gitlab.net/app/discover#/?_a=%28index:%27ed942d00-5186-11ea-ad8a-f3610a492295%27%2Cquery%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foobar%27%29%29&_g=%28time%3A%28from%3A%272022-11-13T00:00:00.000Z%27%2Cto%3A%272022-11-14T00:00:00.000Z%27%29%29 ERROR end end diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb index 0ec27da7277..e0bfccf5e78 100644 --- a/qa/spec/resource/base_spec.rb +++ b/qa/spec/resource/base_spec.rb @@ -94,9 +94,9 @@ RSpec.describe QA::Resource::Base do end end - context 'when FIPS mode is enabled' do + context 'when personal_access_tokens_disabled returns true' do before do - stub_env('FIPS', '1') + stub_env('PERSONAL_ACCESS_TOKENS_DISABLED', true) end it 'calls .fabricate_via_browser_ui!' do @@ -108,7 +108,7 @@ RSpec.describe QA::Resource::Base do end describe '.fabricate_via_api_unless_fips!' do - context 'when FIPS mode is not enabled' do + context 'when personal_access_tokens_disabled returns false' do it 'calls .fabricate_via_api!!' do expect(described_class).to receive(:fabricate_via_api!) @@ -116,9 +116,9 @@ RSpec.describe QA::Resource::Base do end end - context 'when FIPS mode is enabled' do + context 'when personal_access_tokens_disabled returns true' do before do - stub_env('FIPS', '1') + stub_env('PERSONAL_ACCESS_TOKENS_DISABLED', true) end it 'calls .fabricate_via_browser_ui!' do diff --git a/qa/spec/resource/user_spec.rb b/qa/spec/resource/user_spec.rb index d1fc02ff033..547c27dc2ff 100644 --- a/qa/spec/resource/user_spec.rb +++ b/qa/spec/resource/user_spec.rb @@ -143,4 +143,27 @@ RSpec.describe QA::Resource::User do end end end + + describe '#fabricate_or_use' do + # Signup Disabled, Personal Access Tokens disabled, method used, method that is not used + [ + [true, false, :fabricate_via_api!, :fabricate!], + [false, false, :fabricate!, :fabricate_via_api!], + [false, true, :fabricate!, :fabricate_via_api!], + [true, true, :fabricate!, :fabricate_via_api!] + ].each do |signup_disabled, personal_access_tokens_disabled, method_used, method_not_used| + it "when signup_disabled is #{signup_disabled}, "\ + "personal_access_tokens_disabled is #{personal_access_tokens_disabled}, "\ + "calls #{method_used}, does not call #{method_not_used}" do + allow(QA::Runtime::Env).to receive(:signup_disabled?).and_return(signup_disabled) + allow(QA::Runtime::Env).to receive(:personal_access_tokens_disabled?) + .and_return(personal_access_tokens_disabled) + + expect(described_class).to receive(method_used) + expect(described_class).not_to receive(method_not_used) + + described_class.fabricate_or_use + end + end + end end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 61d91da8738..e9c2000681b 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -7,7 +7,7 @@ RSpec.describe QA::Runtime::Env do it_behaves_like 'boolean method with parameter', kwargs end - shared_examples 'boolean method with parameter' do |method:, param: nil, env_key:, default:| + shared_examples 'boolean method with parameter' do |method:, env_key:, default:, param: nil| context 'when there is an env variable set' do it 'returns false when falsey values specified' do stub_env(env_key, 'false') diff --git a/qa/spec/support/formatters/test_metrics_formatter_spec.rb b/qa/spec/support/formatters/test_metrics_formatter_spec.rb index 76bde98cc33..2b8a0791dc5 100644 --- a/qa/spec/support/formatters/test_metrics_formatter_spec.rb +++ b/qa/spec/support/formatters/test_metrics_formatter_spec.rb @@ -253,6 +253,27 @@ describe QA::Support::Formatters::TestMetricsFormatter do end end + context 'with additional custom metrics' do + it 'exports data to influxdb with additional metrics' do + run_spec do + it( + 'spec', + custom_test_metrics: { tags: { custom_tag: "tag" }, fields: { custom_field: 1 } }, + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234' + ) {} + end + + custom_data = data.merge({ + **data, + tags: data[:tags].merge({ custom_tag: "tag" }), + fields: data[:fields].merge({ custom_field: 1 }) + }) + + expect(influx_write_api).to have_received(:write).once + expect(influx_write_api).to have_received(:write).with(data: [custom_data]) + end + end + context 'with fabrication runtimes' do let(:api_fabrication) { 4 } let(:ui_fabrication) { 10 } diff --git a/qa/spec/support/loglinking_spec.rb b/qa/spec/support/loglinking_spec.rb index 10865068e3d..3955d266ef6 100644 --- a/qa/spec/support/loglinking_spec.rb +++ b/qa/spec/support/loglinking_spec.rb @@ -2,40 +2,57 @@ RSpec.describe QA::Support::Loglinking do describe '.failure_metadata' do - context 'return nil string' do - it 'if correlation_id is empty' do + context 'when correlation_id does not exist' do + it 'returns nil when correlation_id is empty' do expect(QA::Support::Loglinking.failure_metadata('')).to eq(nil) end - it 'if correlation_id is nil' do + it 'returns nil when correlation_id is nil' do expect(QA::Support::Loglinking.failure_metadata(nil)).to eq(nil) end end - context 'return error string' do - it 'with sentry URL' do - allow(QA::Support::Loglinking).to receive(:sentry_url).and_return('https://sentry.address/?environment=bar') - allow(QA::Support::Loglinking).to receive(:kibana_url).and_return(nil) + context 'when correlation_id exists' do + context 'and logging environment exists' do + it 'returns Sentry URL' do + allow(QA::Support::Loglinking).to receive(:get_logging_environment).and_return(:foo) + allow(QA::Support::Loglinking).to receive(:get_sentry_base_url).and_return('https://sentry.address/?environment=bar') + allow(QA::Support::Loglinking).to receive(:get_kibana_base_url).and_return(nil) + allow(QA::Support::Loglinking).to receive(:get_kibana_index).and_return(nil) - expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp) + expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp) Correlation Id: foo123 Sentry Url: https://sentry.address/?environment=bar&query=correlation_id%3A%22foo123%22 - ERROR - end + ERROR + end + + it 'returns Kibana URL' do + time = Time.new(2022, 11, 14, 0, 0, 0, '+00:00') - it 'with kibana URL' do - allow(QA::Support::Loglinking).to receive(:sentry_url).and_return(nil) - allow(QA::Support::Loglinking).to receive(:kibana_url).and_return('https://kibana.address/') + allow(QA::Support::Loglinking).to receive(:get_logging_environment).and_return(:foo) + allow(QA::Support::Loglinking).to receive(:get_sentry_base_url).and_return(nil) + allow(QA::Support::Loglinking).to receive(:get_kibana_base_url).and_return('https://kibana.address/') + allow(QA::Support::Loglinking).to receive(:get_kibana_index).and_return('pubsub-rails-inf-foo') + allow(Time).to receive(:now).and_return(time) - expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp) + expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql(<<~ERROR.chomp) Correlation Id: foo123 - Kibana Url: https://kibana.address/app/discover#/?_a=%28query%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foo123%27%29%29&_g=%28time%3A%28from%3Anow-24h%2Cto%3Anow%29%29 - ERROR + Kibana Url: https://kibana.address/app/discover#/?_a=%28index:%27pubsub-rails-inf-foo%27%2Cquery%3A%28language%3Akuery%2Cquery%3A%27json.correlation_id%20%3A%20foo123%27%29%29&_g=%28time%3A%28from%3A%272022-11-13T00:00:00.000Z%27%2Cto%3A%272022-11-14T00:00:00.000Z%27%29%29 + ERROR + end + end + + context 'and logging environment does not exist' do + it 'returns only the correlation ID' do + allow(QA::Support::Loglinking).to receive(:get_logging_environment).and_return(nil) + + expect(QA::Support::Loglinking.failure_metadata('foo123')).to eql('Correlation Id: foo123') + end end end end - describe '.sentry_url' do + describe '.get_sentry_base_url' do let(:url_hash) do { :staging => 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg', @@ -47,36 +64,52 @@ RSpec.describe QA::Support::Loglinking do } end - it 'returns sentry URL if environment found' do + it 'returns Sentry base URL based on environment' do url_hash.each do |environment, url| - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(environment) - - expect(QA::Support::Loglinking.sentry_url).to eq(url) + expect(QA::Support::Loglinking.get_sentry_base_url(environment)).to eq(url) end end end - describe '.kibana_url' do + describe '.get_kibana_base_url' do let(:url_hash) do { :staging => 'https://nonprod-log.gitlab.net/', :staging_ref => nil, :production => 'https://log.gprd.gitlab.net/', + :pre => 'https://nonprod-log.gitlab.net/', :foo => nil, nil => nil } end - it 'returns kibana URL if environment found' do + it 'returns Kibana URL based on environment' do url_hash.each do |environment, url| - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(environment) + expect(QA::Support::Loglinking.get_kibana_base_url(environment)).to eq(url) + end + end + end + + describe '.get_kibana_index' do + let(:index_hash) do + { + :staging => 'ed942d00-5186-11ea-ad8a-f3610a492295', + :staging_ref => nil, + :production => '7092c4e2-4eb5-46f2-8305-a7da2edad090', + :pre => 'pubsub-rails-inf-pre', + :foo => nil, + nil => nil + } + end - expect(QA::Support::Loglinking.kibana_url).to eq(url) + it 'returns Kibana index based on environment' do + index_hash.each do |environment, index| + expect(QA::Support::Loglinking.get_kibana_index(environment)).to eq(index) end end end - describe '.logging_environment' do + describe '.get_logging_environment' do let(:staging_address) { 'https://staging.gitlab.com' } let(:staging_ref_address) { 'https://staging-ref.gitlab.com' } let(:production_address) { 'https://gitlab.com' } @@ -110,23 +143,7 @@ RSpec.describe QA::Support::Loglinking do logging_env_array.each do |logging_env_hash| allow(QA::Runtime::Scenario).to receive(:attributes).and_return({ gitlab_address: logging_env_hash[:address] }) - expect(QA::Support::Loglinking.logging_environment).to eq(logging_env_hash[:expected_env]) - end - end - end - - describe '.logging_environment?' do - context 'returns boolean' do - it 'returns true if logging_environment is not nil' do - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(:staging) - - expect(QA::Support::Loglinking.logging_environment?).to eq(true) - end - - it 'returns false if logging_environment is nil' do - allow(QA::Support::Loglinking).to receive(:logging_environment).and_return(nil) - - expect(QA::Support::Loglinking.logging_environment?).to eq(false) + expect(QA::Support::Loglinking.get_logging_environment).to eq(logging_env_hash[:expected_env]) end end end diff --git a/qa/spec/support/page_error_checker_spec.rb b/qa/spec/support/page_error_checker_spec.rb index 735c0f83ecd..5ccbe869dfd 100644 --- a/qa/spec/support/page_error_checker_spec.rb +++ b/qa/spec/support/page_error_checker_spec.rb @@ -298,7 +298,7 @@ RSpec.describe QA::Support::PageErrorChecker do expect(page).to receive(:execute_script) expect(QA::Runtime::Logger).to receive(:debug).with("Fetching API error cache for #{page_url}") - expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR.chomp) + expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR) Interceptor Api Errors [500] GET https://foo.bar -- Correlation Id: 12345 ERROR @@ -318,7 +318,7 @@ RSpec.describe QA::Support::PageErrorChecker do expect(page).to receive(:execute_script) expect(QA::Runtime::Logger).to receive(:debug).with("Fetching API error cache for #{page_url}") - expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR.chomp).exactly(1).time + expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR).exactly(1).time Interceptor Api Errors [500] GET https://foo.bar -- Correlation Id: 12345 ERROR @@ -338,7 +338,7 @@ RSpec.describe QA::Support::PageErrorChecker do expect(page).to receive(:execute_script) expect(QA::Runtime::Logger).to receive(:debug).with("Fetching API error cache for #{page_url}") - expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR.chomp) + expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR) Interceptor Api Errors [500] GET https://foo.bar -- Correlation Id: 12345 ERROR @@ -346,6 +346,28 @@ RSpec.describe QA::Support::PageErrorChecker do QA::Support::PageErrorChecker.log_request_errors(page) end + it 'logs graphql errors if any exist' do + error = { + 'url' => 'https://foo.bar?query={ sensitive-data: 12345 }', + 'status' => 200, + 'method' => 'POST', + 'errorData' => 'error-messages: Something bad happened', + 'headers' => { 'x-request-id' => '12345' } + } + expect(page).to receive(:driver).and_return(driver) + expect(page).to receive(:execute_script).and_return({ 'errors' => [error] }) + expect(page).to receive(:execute_script) + + expect(QA::Runtime::Logger).to receive(:debug).with("Fetching API error cache for #{page_url}") + expect(QA::Runtime::Logger).to receive(:error).with(<<~ERROR.chomp) + Interceptor Api Errors + [200] POST https://foo.bar -- Correlation Id: 12345 + error-messages: Something bad happened + ERROR + + QA::Support::PageErrorChecker.log_request_errors(page) + end + it 'returns if cache is nil' do expect(page).to receive(:driver).and_return(driver) expect(page).to receive(:execute_script).and_return(nil) @@ -365,17 +387,11 @@ RSpec.describe QA::Support::PageErrorChecker do end end stub_const('Logs', logs_class) - manage_class = Class.new do + browser_class = Class.new do def self.logs Logs end end - stub_const('Manage', manage_class) - browser_class = Class.new do - def self.manage - Manage - end - end stub_const('Browser', browser_class) driver_class = Class.new do def self.browser diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb index f08af8a717a..9786d247d5e 100644 --- a/qa/spec/tools/reliable_report_spec.rb +++ b/qa/spec/tools/reliable_report_spec.rb @@ -22,8 +22,8 @@ describe QA::Tools::ReliableReport do "stage" => "manage", "_time" => time } - { - 0 => instance_double( + [ + instance_double( "InfluxDB2::FluxTable", records: [ instance_double("InfluxDB2::FluxRecord", values: values), @@ -31,7 +31,7 @@ describe QA::Tools::ReliableReport do instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s })) ] ) - } + ] end let(:reliable_runs) do @@ -42,8 +42,8 @@ describe QA::Tools::ReliableReport do "stage" => "create", "_time" => time } - { - 0 => instance_double( + [ + instance_double( "InfluxDB2::FluxTable", records: [ instance_double("InfluxDB2::FluxRecord", values: { **values, "status" => "passed" }), @@ -51,12 +51,12 @@ describe QA::Tools::ReliableReport do instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s })) ] ) - } + ] end def flux_query(reliable:) <<~QUERY - from(bucket: "e2e-test-stats") + from(bucket: "e2e-test-stats-main") |> range(start: -#{range}d) |> filter(fn: (r) => r._measurement == "test-stats") |> filter(fn: (r) => r.run_type == "staging-full" or |