diff options
Diffstat (limited to 'qa')
121 files changed, 1835 insertions, 972 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile index 8e79d0a7bad..6efc8ac09fa 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -15,11 +15,11 @@ ENV WD_INSTALL_DIR=/usr/local/bin ## # Install system libs # -RUN apt-get update; \ - apt-get install -y xvfb unzip; \ - apt-get -yq autoremove; \ - apt-get clean -yqq; \ - rm -rf /var/lib/apt/lists/* +RUN apt-get update \ + && apt-get install -y xvfb unzip \ + && apt-get -yq autoremove \ + && apt-get clean -yqq \ + && rm -rf /var/lib/apt/lists/* ## # Install root certificate diff --git a/qa/Gemfile b/qa/Gemfile index 53dc071b766..9e41b5ddeed 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,21 +2,21 @@ source 'https://rubygems.org' -gem 'gitlab-qa', '~> 8', '>= 8.15.1', require: 'gitlab/qa' -gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile +gem 'gitlab-qa', '~> 9', require: 'gitlab/qa' +gem 'activesupport', '~> 6.1.7.2' # This should stay in sync with the root's Gemfile 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.7', '>= 4.7.1' +gem 'selenium-webdriver', '~> 4.8' 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' gem 'rspec_junit_formatter', '~> 0.6.0' -gem 'faker', '~> 3.1' +gem 'faker', '~> 3.1', '>= 3.1.1' gem 'knapsack', '~> 4.0' -gem 'parallel_tests', '~> 4.1' +gem 'parallel_tests', '~> 4.2' gem 'rotp', '~> 6.2.2' gem 'parallel', '~> 1.22', '>= 1.22.1' gem 'rainbow', '~> 3.1.1' @@ -24,7 +24,7 @@ gem 'rspec-parameterized', '~> 1.0.0' gem 'octokit', '~> 6.0.1' gem "faraday-retry", "~> 2.0" gem 'webdrivers', '~> 5.2' -gem 'zeitwerk', '~> 2.6', '>= 2.6.6' +gem 'zeitwerk', '~> 2.6', '>= 2.6.7' gem 'influxdb-client', '~> 2.9' gem 'terminal-table', '~> 3.0.2', require: false gem 'slack-notifier', '~> 2.4', require: false @@ -38,9 +38,9 @@ gem 'chemlab', '~> 0.10' gem 'chemlab-library-www-gitlab-com', '~> 0.1', '>= 0.1.1' # dependencies for jenkins client -gem 'nokogiri', '~> 1.14' +gem 'nokogiri', '~> 1.14', '>= 1.14.2' -gem 'deprecation_toolkit', '~> 2.0.1', require: false +gem 'deprecation_toolkit', '~> 2.0.3', require: false group :development do gem 'pry-byebug', '~> 3.10.1', platform: :mri diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 577b641685a..d544aa685a5 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.1.4.7) + activesupport (6.1.7.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -57,13 +57,13 @@ GEM zeitwerk (>= 2.5, < 3) debug_inspector (1.1.0) declarative (0.0.20) - deprecation_toolkit (2.0.1) + deprecation_toolkit (2.0.3) activesupport (>= 5.2) diff-lcs (1.3) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) excon (0.92.4) - faker (3.1.0) + faker (3.1.1) i18n (>= 1.8.11, < 2) faraday (2.5.2) faraday-net_http (>= 2.0, < 3.1) @@ -102,7 +102,7 @@ GEM gitlab (4.18.0) httparty (~> 0.18) terminal-table (>= 1.5.1) - gitlab-qa (8.15.1) + gitlab-qa (9.0.0) activesupport (~> 6.1) gitlab (~> 4.18.0) http (~> 5.0) @@ -176,11 +176,11 @@ GEM mime-types-data (3.2022.0105) mini_mime (1.1.0) mini_portile2 (2.8.1) - minitest (5.16.3) + minitest (5.17.0) multi_json (1.15.0) multi_xml (0.6.0) netrc (0.11.0) - nokogiri (1.14.0) + nokogiri (1.14.2) mini_portile2 (~> 2.8.0) racc (~> 1.4) octokit (6.0.1) @@ -189,7 +189,7 @@ GEM oj (3.13.23) os (1.1.4) parallel (1.22.1) - parallel_tests (4.1.0) + parallel_tests (4.2.0) parallel parser (3.1.3.0) ast (~> 2.4.1) @@ -259,7 +259,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - selenium-webdriver (4.7.1) + selenium-webdriver (4.8.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -298,13 +298,13 @@ GEM websocket (1.2.9) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.6) + zeitwerk (2.6.7) PLATFORMS ruby DEPENDENCIES - activesupport (~> 6.1.4.7) + activesupport (~> 6.1.7.2) airborne (~> 0.3.7) allure-rspec (~> 2.20.0) capybara (~> 3.38.0) @@ -312,18 +312,18 @@ DEPENDENCIES chemlab (~> 0.10) chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1) confiner (~> 0.4) - deprecation_toolkit (~> 2.0.1) - faker (~> 3.1) + deprecation_toolkit (~> 2.0.3) + faker (~> 3.1, >= 3.1.1) faraday-retry (~> 2.0) fog-core (= 2.1.0) fog-google (~> 1.19) - gitlab-qa (~> 8, >= 8.15.1) + gitlab-qa (~> 9) influxdb-client (~> 2.9) knapsack (~> 4.0) - nokogiri (~> 1.14) + nokogiri (~> 1.14, >= 1.14.2) octokit (~> 6.0.1) parallel (~> 1.22, >= 1.22.1) - parallel_tests (~> 4.1) + parallel_tests (~> 4.2) pry-byebug (~> 3.10.1) rainbow (~> 3.1.1) rake (~> 13, >= 13.0.6) @@ -334,12 +334,12 @@ DEPENDENCIES rspec-retry (~> 0.6.2) rspec_junit_formatter (~> 0.6.0) ruby-debug-ide (~> 0.7.3) - selenium-webdriver (~> 4.7, >= 4.7.1) + selenium-webdriver (~> 4.8) slack-notifier (~> 2.4) terminal-table (~> 3.0.2) warning (~> 1.3) webdrivers (~> 5.2) - zeitwerk (~> 2.6, >= 2.6.6) + zeitwerk (~> 2.6, >= 2.6.7) BUNDLED WITH - 2.3.26 + 2.4.6 diff --git a/qa/gdk/Dockerfile b/qa/gdk/Dockerfile new file mode 100644 index 00000000000..ed8f3f317eb --- /dev/null +++ b/qa/gdk/Dockerfile @@ -0,0 +1,50 @@ +FROM registry.gitlab.com/gitlab-org/gitlab-development-kit/asdf-bootstrapped-gdk-installed-gitlab-e2e:ml-create-image-for-gitlab-qa-tests + +ENV CHROME_DRIVER_VERSION="107.0.5304.62" +ENV CHROME_VERSION="107.0.5304.87-1" +ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb" +ENV CHROME_URL="https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab-build-images/packages/generic/google-chrome-stable/${CHROME_VERSION}/${CHROME_DEB}" + +WORKDIR /home/gdk/gdk + +COPY --chown=gdk qa/gdk/gdk.yml . + +RUN cat gdk.yml && \ + gdk update && \ + gdk restart && \ + ./support/test_url http://gdk.test:3000 && \ + gdk stop && sleep 5 && \ + GDK_KILL_CONFIRM=true gdk kill && \ + ps -ef && \ + cd gitlab && git reset --hard && \ + sudo rm -rf "$HOME/gdk/gitaly/_build/deps/git/source" \ + "$HOME/gdk/gitaly/_build/deps/libgit2/source" \ + "$HOME/gdk/gitaly/_build/cache" \ + "$HOME/gdk/gitaly/_build/deps" \ + "$HOME/gdk/gitaly/_build/intermediate" \ + "$HOME/.cache/" \ + "$HOME/gdk/gdk/gitlab" \ + /tmp/* + +# Install Google Chrome version with headless support +# Download from our local S3 bucket, populated by https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/scripts/cache-google-chrome +# +RUN echo "${CHROME_URL}" && \ + curl --silent --show-error --fail -O "${CHROME_URL}" && \ + sudo apt update && \ + sudo dpkg -i "./${CHROME_DEB}" || true && \ + sudo apt install -f -y && \ + rm -f "./${CHROME_DEB}" + +WORKDIR /home/gdk/gdk/gitlab + +RUN bundle install --jobs=$(nproc) --retry=3 --quiet +RUN cd qa && \ + bundle install --jobs=$(nproc) --retry=3 --quiet && \ + bundle exec rake -f tasks/webdrivers.rake webdrivers:chromedriver:update[${CHROME_DRIVER_VERSION}] + +RUN git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + +COPY --chown=gdk qa/gdk/launch . + +ENTRYPOINT ["./launch"] diff --git a/qa/gdk/gdk.yml b/qa/gdk/gdk.yml new file mode 100644 index 00000000000..0494cd0d3c1 --- /dev/null +++ b/qa/gdk/gdk.yml @@ -0,0 +1,26 @@ +--- +hostname: gdk.test +sshd: + additional_config: 'AcceptEnv GIT_PROTOCOL' +webpack: + live_reload: false + sourcemaps: false + incremental: false +gdk: + ask_to_restart_after_update: false + auto_reconfigure: false + overwrite_changes: true + quiet: false +gitlab: + rails: + bootsnap: false + hostname: gdk.test +gitlab_k8s_agent: + enabled: false +gitlab_pages: + enabled: false +prometheus: + enabled: false +tracer: + jaeger: + enabled: false diff --git a/qa/gdk/launch b/qa/gdk/launch new file mode 100755 index 00000000000..4b1fc6ae191 --- /dev/null +++ b/qa/gdk/launch @@ -0,0 +1,40 @@ +#!/bin/bash + +COMMIT_REF=${1:-$CI_COMMIT_REF_SLUG} +RSPEC_ARGS=$2 + +if [ -z "${COMMIT_REF}" ]; then + echo "Please provide a commit ref with the code to be tested as the first argument" + exit 1 +fi + +# Set the GitLab license mode to "test" so that GitLab uses the appropriate encryption key +export GITLAB_LICENSE_MODE="test" + +# Create the temporary directory that screenshots are saved to +sudo install -m 777 -d /home/gdk/gdk/gitlab/qa/tmp + +# Update GDK +(cd .. ; gdk update ; cat gdk.yml) + +# Reset, fetch, and checkout the GitLab repository with the code from the ref to be tested +git reset --hard +git fetch origin $COMMIT_REF +git checkout $COMMIT_REF + +# Install the required gems +bundle install --jobs=$(nproc) --retry=3 --quiet + +# Run the database migrations +bundle exec rake db:migrate + +# Restart GDK to be sure any changes are accounted for in running services, start any stopped services, and wait until the GDK is reachable +(cd .. ; gdk restart ; ./support/test_url http://gdk.test:3000) + +# Install the required gems in the QA directory +cd qa +bundle install --jobs=$(nproc) --retry=3 --quiet + +# Run the tests +bundle exec rake "knapsack:download[test]" +bundle exec bin/qa Test::Instance::All http://gdk.test:3000 -- $RSPEC_ARGS || true diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb index 62f55aea2cc..3cb501efe13 100644 --- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb +++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb @@ -32,8 +32,7 @@ module Gitlab div :project div :storage_type_legend span :container_registry_size - div :purchased_usage_total_free # Different UI for free namespace - span :purchased_usage_total + div :purchased_usage_total div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ div :additional_storage_alert, text: /purchase additional storage/ @@ -66,14 +65,10 @@ module Gitlab # Returns total purchased storage value once it's ready on page # # @return [Float] Total purchased storage value in GiB - def total_purchased_storage(free_name_space = true) + def total_purchased_storage additional_storage_alert_element.wait_until(&:present?) - if free_name_space - purchased_usage_total_free.split('/').last.match(/\d+\.\d+/)[0].to_f - else - purchased_usage_total.to_f - end + purchased_usage_total[/(\d+){2}.\d+/].to_f end def additional_ci_minutes_added? diff --git a/qa/qa/page/trials/new.rb b/qa/lib/gitlab/page/trials/new.rb index 40f593a7aa7..b2e6cbdb682 100644 --- a/qa/qa/page/trials/new.rb +++ b/qa/lib/gitlab/page/trials/new.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -module QA +module Gitlab module Page module Trials class New < Chemlab::Page diff --git a/qa/qa/page/trials/select.rb b/qa/lib/gitlab/page/trials/select.rb index 39ef604a781..6eaf6003837 100644 --- a/qa/qa/page/trials/select.rb +++ b/qa/lib/gitlab/page/trials/select.rb @@ -1,16 +1,23 @@ # frozen_string_literal: true -module QA +module Gitlab module Page module Trials class Select < Chemlab::Page path '/-/trials/select' - select :subscription_for + button :select_group, 'data-testid': 'base-dropdown-toggle' + div :group_dropdown, 'data-testid': 'base-dropdown-menu' text_field :new_group_name button :start_your_free_trial radio :trial_company radio :trial_individual + + def subscription_for=(group_name) + select_group + + group_dropdown_element.span(text: /#{group_name}/).click + end end end end diff --git a/qa/lib/slack.rb b/qa/lib/slack.rb new file mode 100644 index 00000000000..95e81b700f5 --- /dev/null +++ b/qa/lib/slack.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'chemlab/library' + +module Slack + include Chemlab::Library + + self.base_url = 'https://slack.com' +end diff --git a/qa/lib/slack/mixins/browser.rb b/qa/lib/slack/mixins/browser.rb new file mode 100644 index 00000000000..853ff2c7130 --- /dev/null +++ b/qa/lib/slack/mixins/browser.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Slack + module Mixins + module Browser + def browser + ::Chemlab.configuration.browser.session.engine + end + end + end +end diff --git a/qa/lib/slack/mixins/gitlab_app.rb b/qa/lib/slack/mixins/gitlab_app.rb new file mode 100644 index 00000000000..66b456ef824 --- /dev/null +++ b/qa/lib/slack/mixins/gitlab_app.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Slack + module Mixins + module GitlabApp + # @param [QA::Resource::Project] project + # @param [String] channel + # @param [String] title + # @param [String] description + def create_issue(project, channel:, title:, description:) + lines = [ + "/staging-gitlab #{project.path_with_namespace} issue new #{title}", + description + ] + + send_message_to_channel(lines, channel: channel) + end + + # @param [QA::Resource::Project] project + # @param [QA::Resource::Project] target + # @param [String] id + # @param [String] channel + def move_issue(project, target, id:, channel:) + line = "/staging-gitlab #{project.path_with_namespace} issue move #{id} to #{target.path_with_namespace}" + send_message_to_channel([line], channel: channel) + end + + # @param [QA::Resource::Project] project + # @param [String] id + # @param [String] channel + def show_issue(project, id:, channel:) + send_message_to_channel(["/staging-gitlab #{project.path_with_namespace} issue show #{id}"], channel: channel) + end + + # @param [QA::Resource::Project] project + # @param [String] id + # @param [String] channel + def close_issue(project, id:, channel:) + send_message_to_channel(["/staging-gitlab #{project.path_with_namespace} issue close #{id}"], channel: channel) + end + + # @param [QA::Resource::Project] project + # @param [String] channel + # @param [String] id + # @param [String] comment + def comment_on_issue(project, channel:, id:, comment:) + command = "/staging-gitlab #{project.path_with_namespace} issue comment #{id}" + send_message_to_channel([command, comment], channel: channel) + end + end + end +end diff --git a/qa/lib/slack/page/chat.rb b/qa/lib/slack/page/chat.rb new file mode 100644 index 00000000000..5f8553fc425 --- /dev/null +++ b/qa/lib/slack/page/chat.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Slack + module Page + class Chat < Chemlab::Page + include Mixins::Browser + include Mixins::GitlabApp + + div :message_field, data_qa: 'message_input' + button :connect_gitlab_button, visible_text: /Connect your GitLab account/ + button :skip_download_slack_button, data_qa: 'continue_in_browser' + + def skip_download_screen + wait_for_text('download the Slack app') + + skip_download_slack_button_element.click if skip_download_slack_button_element.exists? + end + + # @param [Array<String>] lines - messages to send + # @param [String] channel to send message to + def send_message_to_channel(lines, channel:) + go_to_channel(channel) + + message_field_element.focus + message_field_element.click + + while line = lines.shift + browser.send_keys(line) + wait_for_text(line) + + browser.send_keys([:shift, :enter]) unless lines.empty? + end + + browser.send_keys(:enter) + end + + def wait_for_text(line) + QA::Support::Waiter.wait_until(max_duration: 3, raise_on_failure: false) do + browser.text.include?(line) + end + end + + def go_to_channel(channel) + menu_item = messages.find do |div| + div.text == channel + end + menu_item.click + end + + def click_connect_account_link + divs = messages(visible_text: /connect your GitLab account/i) + el = divs.last.a(href: /staging-ref/) + el.scroll.to(:top) + el.click + end + + def messages(**opts) + browser.divs(data_qa: 'virtual-list-item', **opts) + end + + def gitlab_app_home + browser.divs(data_qa: 'channel_item_container').find do |el| + el.text == 'GitLab' + end + end + end + end +end diff --git a/qa/lib/slack/page/login.rb b/qa/lib/slack/page/login.rb new file mode 100644 index 00000000000..a11b0d27fd0 --- /dev/null +++ b/qa/lib/slack/page/login.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Slack + module Page + class Login < Chemlab::Page + path '/workspace-signin' + + text_field :workspace_field, data_qa: 'signin_domain_input' + button :continue_button, data_qa: 'submit_team_domain_button' + + link :sign_in_with_password_link, data_qa: 'sign_in_password_link' + + text_field :email_address_field, data_qa: 'login_email' + text_field :password_field, data_qa: 'login_password', type: 'password' + button :sign_in_button, data_qa: 'signin_button' + + def sign_in + navigate_to_workspace + + # sign into with password if needed + sign_in_with_password_link_element.click if sign_in_with_password_link_element.exists? + + finish_sign_in + end + + def navigate_to_workspace + self.workspace_field = ::QA::Runtime::Env.slack_workspace + continue_button + end + + def finish_sign_in + return unless email_address_field_element.exists? + + self.email_address_field = ::QA::Runtime::Env.slack_email + self.password_field = ::QA::Runtime::Env.slack_password + sign_in_button + end + end + end +end diff --git a/qa/lib/slack/page/oauth.rb b/qa/lib/slack/page/oauth.rb new file mode 100644 index 00000000000..700d19f0c4c --- /dev/null +++ b/qa/lib/slack/page/oauth.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Slack + module Page + class Oauth < Chemlab::Page + button :submit_oauth, data_qa: 'oauth_submit_button' + end + end +end @@ -85,11 +85,6 @@ module QA "fips" => "FIPS" ) - # Configure knapsack at the very begining of the setup - loader.on_setup do - QA::Support::KnapsackReport.configure! - end - loader.setup loader.eager_load end diff --git a/qa/qa/fixtures/gpg/admin.asc b/qa/qa/fixtures/gpg/admin.asc Binary files differnew file mode 100644 index 00000000000..d8daa50cfc5 --- /dev/null +++ b/qa/qa/fixtures/gpg/admin.asc diff --git a/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb index 590120ce7b2..f4efbebe264 100644 --- a/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb +++ b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb @@ -1,6 +1,7 @@ pull: - image: dtzar/helm-kubectl:latest + image: alpine:latest script: + - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing - helm repo add --username <%= username %> --password <%= access_token %> gitlab_qa ${CI_API_V4_URL}/projects/<%= package_project.id %>/packages/helm/stable - helm repo update - helm pull gitlab_qa/<%= package_name %> diff --git a/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb index b1c275cd96a..cd218fcc4a2 100644 --- a/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb +++ b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb @@ -1,6 +1,7 @@ deploy: - image: dtzar/helm-kubectl:latest + image: alpine:latest script: + - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing - apk add curl - helm create <%= package_name %> - cp ./Chart.yaml <%= package_name %> diff --git a/qa/qa/flow/alert_settings.rb b/qa/qa/flow/alert_settings.rb index 2a464c16c6d..f5ee4f94065 100644 --- a/qa/qa/flow/alert_settings.rb +++ b/qa/qa/flow/alert_settings.rb @@ -5,13 +5,11 @@ module QA module AlertSettings extend self - def setup_http_endpoint( - integration_name: random_word, - payload: { title: random_word, description: random_word }, - send: true - ) - credentials = {} + def go_to_monitor_settings Page::Project::Menu.perform(&:go_to_monitor_settings) + end + + def setup_http_endpoint_integration(integration_name: random_word) Page::Project::Settings::Monitor.perform do |setting| setting.expand_alerts do |alert| alert.add_new_integration @@ -19,23 +17,11 @@ module QA alert.enter_integration_name(integration_name) alert.activate_integration alert.save_and_create_alert - - if send - alert.fill_in_test_payload(payload.to_json) - alert.send_test_alert - else - alert.go_to_view_credentials - credentials = { url: alert.webhook_url, auth_key: alert.authorization_key } - end end end - - credentials end - def setup_prometheus(payload: { title: random_word, description: random_word }, send: true) - credentials = {} - Page::Project::Menu.perform(&:go_to_monitor_settings) + def setup_prometheus_integration Page::Project::Settings::Monitor.perform do |setting| setting.expand_alerts do |alert| alert.add_new_integration @@ -43,25 +29,87 @@ module QA alert.activate_integration alert.fill_in_prometheus_url alert.save_and_create_alert - - if send - alert.fill_in_test_payload(payload.to_json) - alert.send_test_alert - else - alert.go_to_view_credentials - credentials = { url: alert.webhook_url, auth_key: alert.authorization_key } - end end end + end + + def send_test_alert(integration_type: 'http', payload: nil) + payload ||= integration_type == 'http' ? http_payload : prometheus_payload + + Page::Project::Settings::Alerts.perform do |alert| + alert.fill_in_test_payload(payload.to_json) + alert.send_test_alert + end + end + + def integration_credentials + credentials = {} + Page::Project::Settings::Alerts.perform do |alert| + alert.go_to_view_credentials + credentials = { url: alert.webhook_url, auth_key: alert.authorization_key } + end credentials end + def enable_create_incident + Page::Project::Settings::Monitor.perform do |setting| + setting.expand_alerts do |alert| + alert.go_to_alert_settings + alert.enable_incident_for_alert + alert.save_alert_settings + alert.click_button('Collapse') + end + end + end + + def enable_email_notification + Page::Project::Settings::Monitor.perform do |setting| + setting.expand_alerts do |alert| + alert.go_to_alert_settings + alert.enable_email_notification + alert.save_alert_settings + alert.click_button('Collapse') + end + end + end + private def random_word Faker::Lorem.word end + + def http_payload + { title: random_word, description: random_word } + end + + def prometheus_payload + { + version: '4', + groupKey: nil, + status: 'firing', + receiver: '', + groupLabels: {}, + commonLabels: {}, + commonAnnotations: {}, + externalURL: '', + alerts: [ + { + startsAt: Time.now, + generatorURL: Faker::Internet.url, + endsAt: nil, + status: 'firing', + labels: { gitlab_environment_name: Faker::Lorem.word }, + annotations: + { + title: random_word, + gitlab_y_label: 'status' + } + } + ] + } + end end end end diff --git a/qa/qa/flow/integrations/slack.rb b/qa/qa/flow/integrations/slack.rb new file mode 100644 index 00000000000..8f18ccaa791 --- /dev/null +++ b/qa/qa/flow/integrations/slack.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module QA + module Flow + module Integrations + module Slack + extend self + + # Need to sign in for this method + # @param [QA::Resource::Project] + def start_slack_install(project) + project.visit! + + Page::Project::Menu.perform do |project_menu_page| + project_menu_page.click_project + project_menu_page.go_to_integrations_settings + end + + Page::Project::Settings::Integrations.perform(&:click_slack_application_link) + + EE::Page::Project::Settings::Services::Slack.perform(&:start_slack_install) + ::Slack::Page::Oauth.perform(&:submit_oauth) + end + + # @param [QA::Resource::Project] project + # @option [String | Nil] channel + # @return [Boolean] is this account already authorized? + def start_gitlab_connect(project, channel: nil) + ::Slack::Page::Chat.perform do |chat_page| + # sometimes Slack will present a blocking page + # for downloading the app instead of using a browser + chat_page.skip_download_screen + + lines = ["/staging-gitlab #{project.path_with_namespace} issue show 1"] + chat_page.send_message_to_channel(lines, channel: channel) + + # The only way to know if we are authorized is to send a slash command to the channel. + # If the account / chat_name is already authorized, the Slack app will try to look up the issue + # and return a 404 because it doesn't exist + QA::Support::Waiter.wait_until(max_duration: 4, raise_on_failure: false) do + chat_page.messages.last.text =~ /connect your GitLab account|404 not found!/i + end + + break(true) if chat_page.messages.last.text =~ /404 not found!/i + + chat_page.click_connect_account_link + + false + end + end + end + end + end +end diff --git a/qa/qa/flow/trial.rb b/qa/qa/flow/trial.rb new file mode 100644 index 00000000000..109afeffaa3 --- /dev/null +++ b/qa/qa/flow/trial.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module QA + module Flow + module Trial + extend self + + CUSTOMER_TRIAL_INFO = { + company_name: 'QA Test Company', + number_of_employees: '500 - 1,999', + telephone_number: '555-555-5555', + country: 'United States of America', + state: 'CA' + }.freeze + + def register_for_trial(group: nil) + Gitlab::Page::Trials::New.perform do |new| + new.company_name = CUSTOMER_TRIAL_INFO[:company_name] + new.number_of_employees = CUSTOMER_TRIAL_INFO[:number_of_employees] + new.country = CUSTOMER_TRIAL_INFO[:country] + new.telephone_number = CUSTOMER_TRIAL_INFO[:telephone_number] + new.state = CUSTOMER_TRIAL_INFO[:state] + + new.continue + end + + return unless group + + Gitlab::Page::Trials::Select.perform do |select| + select.subscription_for = group.path + select.trial_company + select.start_your_free_trial + end + end + end + end +end + +QA::Flow::Purchase.prepend_mod_with('Flow::Trial', namespace: QA) diff --git a/qa/qa/flow/user_onboarding.rb b/qa/qa/flow/user_onboarding.rb index 62397d5641d..8c6a50f251f 100644 --- a/qa/qa/flow/user_onboarding.rb +++ b/qa/qa/flow/user_onboarding.rb @@ -10,6 +10,7 @@ module QA if welcome_page.has_get_started_button? welcome_page.select_role('Other') welcome_page.choose_setup_for_company_if_available + welcome_page.choose_create_a_new_project_if_available welcome_page.click_get_started_button end end diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb index a57ef38f768..5ee5d4978e5 100644 --- a/qa/qa/page/component/blob_content.rb +++ b/qa/qa/page/component/blob_content.rb @@ -22,7 +22,7 @@ module QA element :copy_contents_button end - base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue' do + base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer_deprecated.vue' do element :blob_viewer_file_content end end diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb index 90c86688882..71c24b6cac8 100644 --- a/qa/qa/page/component/design_management.rb +++ b/qa/qa/page/component/design_management.rb @@ -78,7 +78,7 @@ module QA end def update_design(filename) - filepath = ::File.join('qa', 'fixtures', 'designs', 'update', filename) + filepath = ::File.join(Runtime::Path.fixtures_path, 'designs', 'update', filename) add_design(filepath) end diff --git a/qa/qa/page/component/dropdown.rb b/qa/qa/page/component/dropdown.rb index c3e0fefee0d..01ef3533ff8 100644 --- a/qa/qa/page/component/dropdown.rb +++ b/qa/qa/page/component/dropdown.rb @@ -4,36 +4,24 @@ 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 + find('li.gl-new-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) + has_css?('li.gl-new-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 + find('span.gl-new-dropdown-button-text').text end def all_items - raise NotImplementedError if use_select2? - - find_all("li.gl-dropdown-item").map(&:text) + find_all("li.gl-new-dropdown-item").map(&: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"]') @@ -44,9 +32,9 @@ module QA end def search_item(item_text) - return super if use_select2? + QA::Runtime::Logger.info "Searching in dropdown: \"#{item_text}\"" - find('div.gl-listbox-search input[type="Search"]').set(item_text) + find('div.gl-listbox-search input[type="Search"]').set(item_text, rapid: false) wait_for_search_to_complete end @@ -56,10 +44,6 @@ module QA 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) @@ -70,9 +54,7 @@ module QA end def search_and_select_exact(item_text) - return super if use_select2? - - QA::Runtime::Logger.info "Searching and selecting: #{item_text}" + QA::Runtime::Logger.info "Searching and selecting exact: \"#{item_text}\"" search_item(item_text) @@ -80,18 +62,14 @@ module QA 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 + find('li.gl-new-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 + find('.gl-new-dropdown-toggle').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) @@ -99,23 +77,12 @@ module QA end def dropdown_open? - return super if use_select2? - - has_css?('ul.gl-dropdown-contents', wait: 1) + has_css?('ul.gl-new-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 diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb deleted file mode 100644 index eb791594d22..00000000000 --- a/qa/qa/page/component/invite_members_modal.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Component - module InviteMembersModal - extend QA::Page::PageConcern - - def self.included(base) - super - - base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do - element :invite_button - element :access_level_dropdown - element :invite_members_modal_content - end - - base.view 'app/assets/javascripts/invite_members/components/group_select.vue' do - element :group_select_dropdown_search_field - element :group_select_dropdown_item - end - - base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do - element :members_token_select_input - end - - base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do - element :invite_a_group_button - end - - base.view 'app/assets/javascripts/invite_members/constants.js' do - element :invite_members_button - end - end - - def open_invite_members_modal - click_element :invite_members_button - end - - def open_invite_group_modal - click_element :invite_a_group_button - end - - def add_member(username, access_level = 'Developer', refresh_page: true) - open_invite_members_modal - - within_element(:invite_members_modal_content) do - fill_element(:members_token_select_input, username) - Support::WaitForRequests.wait_for_requests - click_button(username, match: :prefer_exact) - set_access_level(access_level) - end - - send_invite(refresh_page) - end - - def invite_group(group_name, access_level = 'Guest', refresh_page: true) - open_invite_group_modal - - within_element(:invite_members_modal_content) do - click_button 'Select a group' - - Support::Waiter.wait_until { has_element?(:group_select_dropdown_item) } - - fill_element :group_select_dropdown_search_field, group_name - Support::WaitForRequests.wait_for_requests - click_button group_name - - set_access_level(access_level) - end - - send_invite(refresh_page) - end - - def send_invite(refresh = false) - click_element :invite_button - Support::WaitForRequests.wait_for_requests - page.refresh if refresh - end - - private - - def set_access_level(access_level) - # Guest option is selected by default, skipping these steps if desired option is 'Guest' - select_element(:access_level_dropdown, access_level) unless access_level == 'Guest' - end - end - end - end -end diff --git a/qa/qa/page/component/members/invite_members_modal.rb b/qa/qa/page/component/members/invite_members_modal.rb new file mode 100644 index 00000000000..0220bb1e4fc --- /dev/null +++ b/qa/qa/page/component/members/invite_members_modal.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Members + module InviteMembersModal + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do + element :invite_button + element :access_level_dropdown + element :invite_members_modal_content + end + + base.view 'app/assets/javascripts/invite_members/components/group_select.vue' do + element :group_select_dropdown_search_field + element :group_select_dropdown_item + end + + base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do + element :members_token_select_input + end + + base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do + element :invite_a_group_button + end + + base.view 'app/assets/javascripts/invite_members/constants.js' do + element :invite_members_button + end + end + + def open_invite_members_modal + click_element :invite_members_button + end + + def open_invite_group_modal + click_element :invite_a_group_button + end + + def add_member(username, access_level = 'Developer', refresh_page: true) + open_invite_members_modal + + within_element(:invite_members_modal_content) do + fill_element(:members_token_select_input, username) + Support::WaitForRequests.wait_for_requests + click_button(username, match: :prefer_exact) + set_access_level(access_level) + end + + send_invite(refresh_page) + end + + def invite_group(group_name, access_level = 'Guest', refresh_page: true) + open_invite_group_modal + + within_element(:invite_members_modal_content) do + click_button 'Select a group' + + Support::Waiter.wait_until { has_element?(:group_select_dropdown_item) } + + fill_element :group_select_dropdown_search_field, group_name + Support::WaitForRequests.wait_for_requests + click_button group_name + + set_access_level(access_level) + end + + send_invite(refresh_page) + end + + def send_invite(refresh = false) + click_element :invite_button + Support::WaitForRequests.wait_for_requests + page.refresh if refresh + end + + private + + def set_access_level(access_level) + # Guest option is selected by default, skipping these steps if desired option is 'Guest' + select_element(:access_level_dropdown, access_level) unless access_level == 'Guest' + end + end + end + end + end +end diff --git a/qa/qa/page/component/members/members_filter.rb b/qa/qa/page/component/members/members_filter.rb new file mode 100644 index 00000000000..8803211ea86 --- /dev/null +++ b/qa/qa/page/component/members/members_filter.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Members + module MembersFilter + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue' do + element :search_bar_input + element :search_button + end + end + + def search_member(username) + fill_element :search_bar_input, username + click_element :search_button + end + end + end + end + end +end diff --git a/qa/qa/page/component/members/members_table.rb b/qa/qa/page/component/members/members_table.rb new file mode 100644 index 00000000000..46010a0f9ab --- /dev/null +++ b/qa/qa/page/component/members/members_table.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Members + module MembersTable + extend QA::Page::PageConcern + + def self.included(base) + super + + base.class_eval do + include MembersFilter + include RemoveMemberModal + include RemoveGroupModal + end + + base.view 'app/assets/javascripts/members/components/table/members_table.vue' do + element :member_row + end + + base.view 'app/assets/javascripts/members/components/table/role_dropdown.vue' do + element :access_level_dropdown + element :access_level_link + end + + base.view 'app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue' do + element :user_action_dropdown + end + + base.view 'app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue' do + element :delete_member_dropdown_item + end + + base.view 'app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue' do + element :approve_access_request_button + end + + base.view 'app/assets/javascripts/members/components/members_tabs.vue' do + element :groups_list_tab + end + + base.view 'app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue' do + element :remove_group_link_button + end + end + + def update_access_level(username, access_level) + search_member(username) + + within_element(:member_row, text: username) do + click_element :access_level_dropdown + click_element :access_level_link, text: access_level + end + end + + def remove_member(username) + within_element(:member_row, text: username) do + click_element :user_action_dropdown + click_element :delete_member_dropdown_item + end + + confirm_remove_member + end + + def approve_access_request(username) + within_element(:member_row, text: username) do + click_element :approve_access_request_button + end + end + + def deny_access_request(username) + within_element(:member_row, text: username) do + click_element :delete_member_button + end + + confirm_remove_member + end + + def remove_group(group_name) + click_element :groups_list_tab + + within_element(:member_row, text: group_name) do + click_element :remove_group_link_button + end + + confirm_remove_group + end + + def has_group?(group_name) + click_element :groups_list_tab + has_element?(:member_row, text: group_name) + end + end + end + end + end +end diff --git a/qa/qa/page/component/members/remove_group_modal.rb b/qa/qa/page/component/members/remove_group_modal.rb new file mode 100644 index 00000000000..03c51757b62 --- /dev/null +++ b/qa/qa/page/component/members/remove_group_modal.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Members + module RemoveGroupModal + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/members/components/modals/remove_group_link_modal.vue' do + element :remove_group_link_modal_content + element :remove_group_button + end + end + + def confirm_remove_group + within_element(:remove_group_link_modal_content) do + wait_for_enabled_remove_group_button + + click_element :remove_group_button + end + end + + private + + def wait_for_enabled_remove_group_button + retry_until(sleep_interval: 1, message: 'Waiting for remove group button to be enabled') do + has_element?(:remove_group_button, disabled: false, wait: 3) + end + end + end + end + end + end +end diff --git a/qa/qa/page/component/members/remove_member_modal.rb b/qa/qa/page/component/members/remove_member_modal.rb new file mode 100644 index 00000000000..b7b81040ba7 --- /dev/null +++ b/qa/qa/page/component/members/remove_member_modal.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Members + module RemoveMemberModal + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do + element :remove_member_modal + element :remove_member_button + end + end + + def confirm_remove_member + within_element(:remove_member_modal) do + wait_for_enabled_remove_member_button + + click_element :remove_member_button + end + end + + private + + def wait_for_enabled_remove_member_button + retry_until(sleep_interval: 1, message: 'Waiting for remove member button to be enabled') do + has_element?(:remove_member_button, disabled: false, wait: 3) + end + end + end + end + end + end +end diff --git a/qa/qa/page/component/members_filter.rb b/qa/qa/page/component/members_filter.rb deleted file mode 100644 index fce4560d255..00000000000 --- a/qa/qa/page/component/members_filter.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Component - module MembersFilter - extend QA::Page::PageConcern - - def self.included(base) - super - - base.view 'app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue' do - element :search_bar_input - element :search_button - end - end - - def search_member(username) - fill_element :search_bar_input, username - click_element :search_button - end - end - end - end -end diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb deleted file mode 100644 index 7a835af2575..00000000000 --- a/qa/qa/page/component/select2.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Component - module Select2 - def select_item(item_text) - find('.select2-result-label', text: item_text, match: :prefer_exact).click - end - - def has_item?(item_text) - has_css?('.select2-result-label', text: item_text, match: :prefer_exact) - end - - def current_selection - find('.select2-chosen').text - end - - def clear_current_selection_if_present - if has_css?('a > abbr.select2-search-choice-close', wait: 1.0) - find('a > abbr.select2-search-choice-close').click - end - end - - def search_item(item_text) - find('.select2-input').set(item_text) - - wait_for_search_to_complete - end - - def search_and_select(item_text) - QA::Runtime::Logger.info "Searching and selecting: #{item_text}" - - search_item(item_text) - - raise QA::Page::Base::ElementNotFound, %Q(Couldn't find option named "#{item_text}") unless has_item?(item_text) - - select_item(item_text) - end - - def search_and_select_exact(item_text) - QA::Runtime::Logger.info "Searching and selecting: #{item_text}" - - search_item(item_text) - - raise QA::Page::Base::ElementNotFound, %Q(Couldn't find option named "#{item_text}") unless has_item?(item_text) - - find('.select2-result-label', text: item_text, exact_text: true).click - end - - def expand_select_list - find('span.select2-arrow').click - end - - def wait_for_search_to_complete - Support::WaitForRequests.wait_for_requests - - has_css?('.select2-active', wait: 1) - has_no_css?('.select2-active', wait: 30) - end - - def dropdown_open? - find('.select2-focusser').disabled? - end - end - end - end -end diff --git a/qa/qa/page/component/wiki_sidebar.rb b/qa/qa/page/component/wiki_sidebar.rb index dfb912a1d0b..7543b9655f9 100644 --- a/qa/qa/page/component/wiki_sidebar.rb +++ b/qa/qa/page/component/wiki_sidebar.rb @@ -20,6 +20,7 @@ module QA base.view 'app/views/shared/wikis/_wiki_directory.html.haml' do element :wiki_directory_content + element :wiki_dir_page_link end end @@ -42,6 +43,10 @@ module QA def has_directory?(directory) has_element?(:wiki_directory_content, text: directory) end + + def has_dir_page?(dir_page) + has_element?(:wiki_dir_page_link, page_name: dir_page) + end end end end diff --git a/qa/qa/page/dashboard/todos.rb b/qa/qa/page/dashboard/todos.rb index d5660823118..a65bba9ac39 100644 --- a/qa/qa/page/dashboard/todos.rb +++ b/qa/qa/page/dashboard/todos.rb @@ -8,12 +8,19 @@ module QA view 'app/views/dashboard/todos/index.html.haml' do element :todos_list_container, required: true + element :group_dropdown end view 'app/views/dashboard/todos/_todo.html.haml' do element :todo_item_container element :todo_action_name_content element :todo_target_title_content + element :todo_author_name_content + end + + view 'app/helpers/dropdowns_helper.rb' do + element :dropdown_input_field + element :dropdown_list_content end def has_todo_list? @@ -24,10 +31,39 @@ module QA has_no_element?(:todo_item_container) end - def has_latest_todo_item_with_content?(action, title) + def filter_todos_by_group(group) + click_element :group_dropdown + + fill_element(:dropdown_input_field, group.path) + + within_element(:dropdown_list_content) do + click_on group.path + end + + wait_for_requests + end + + def has_latest_todo_with_author?(author:, action:) + content = { selector: :todo_author_name_content, text: author } + has_latest_todo_with_content?(action, **content) + end + + def has_latest_todo_with_title?(title:, action:) + content = { selector: :todo_target_title_content, text: title } + has_latest_todo_with_content?(action, **content) + end + + def click_todo_with_content(content) + click_element(:todo_item_container, text: content) + end + + private + + def has_latest_todo_with_content?(action, **kwargs) within_element(:todos_list_container) do within_element_by_index(:todo_item_container, 0) do - has_element?(:todo_action_name_content, text: action) && has_element?(:todo_target_title_content, text: title) + has_element?(:todo_action_name_content, text: action) && + has_element?(kwargs[:selector], text: kwargs[:text]) end end end diff --git a/qa/qa/page/group/members.rb b/qa/qa/page/group/members.rb index c7d63b97b4f..7756d3d7f08 100644 --- a/qa/qa/page/group/members.rb +++ b/qa/qa/page/group/members.rb @@ -4,63 +4,8 @@ module QA module Page module Group class Members < Page::Base - include Page::Component::InviteMembersModal - include Page::Component::MembersFilter - - view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do - element :remove_member_modal - end - - view 'app/assets/javascripts/pages/groups/group_members/index.js' do - element :member_row - element :groups_list - element :group_row - end - - view 'app/assets/javascripts/members/components/table/role_dropdown.vue' do - element :access_level_dropdown - element :access_level_link - end - - view 'app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue' do - element :user_action_dropdown - end - - view 'app/assets/javascripts/members/components/action_dropdowns/remove_member_dropdown_item.vue' do - element :delete_member_dropdown_item - end - - view 'app/assets/javascripts/members/components/members_tabs.vue' do - element :groups_list_tab - end - - def update_access_level(username, access_level) - search_member(username) - - within_element(:member_row, text: username) do - click_element :access_level_dropdown - click_element :access_level_link, text: access_level - end - end - - def remove_member(username) - within_element(:member_row, text: username) do - click_element :user_action_dropdown - click_element :delete_member_dropdown_item - end - - within_element(:remove_member_modal) do - click_button("Remove member") - end - end - - def has_existing_group_share?(group_name) - click_element :groups_list_tab - - within_element(:groups_list) do - has_element?(:group_row, text: group_name) - end - end + include Page::Component::Members::InviteMembersModal + include Page::Component::Members::MembersTable end end end diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index de065ca187d..9418593133e 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -44,6 +44,14 @@ module QA end end + def go_to_runners + hover_group_ci_cd do + within_submenu do + click_element(:sidebar_menu_item_link, menu_item: 'Runners') + end + end + end + def go_to_package_settings hover_group_settings do within_submenu do @@ -120,6 +128,14 @@ module QA end end + def hover_group_ci_cd + within_sidebar do + find_element(:sidebar_menu_link, menu_item: 'CI/CD').hover + + yield + end + end + def hover_group_packages within_sidebar do scroll_to_element(:sidebar_menu_link, menu_item: 'Packages and registries') diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb index bb5a3485531..44460291c90 100644 --- a/qa/qa/page/group/settings/general.rb +++ b/qa/qa/page/group/settings/general.rb @@ -123,7 +123,7 @@ module QA wait_for_enabled_transfer_group_button click_element(:transfer_group_button) - fill_confirmation_text(source_group.path) + fill_confirmation_text(source_group.full_path) confirm_transfer end diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index 46ab1e35510..1aab6847b4c 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -15,6 +15,7 @@ module QA view 'app/views/shared/members/_access_request_links.html.haml' do element :leave_group_link + element :request_access_link end def click_subgroup(name) @@ -46,6 +47,10 @@ module QA click_element :leave_group_link click_confirmation_ok_button end + + def click_request_access + click_element :request_access_link + end end end end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 8af78bb86c6..f4f8820bc04 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -76,16 +76,13 @@ module QA end def sign_in_using_admin_credentials - admin = QA::Resource::User.init do |user| - user.username = QA::Runtime::User.admin_username - user.password = QA::Runtime::User.admin_password - end - using_wait_time 0 do set_initial_password_if_present sign_in_using_gitlab_credentials(user: admin) end + set_up_new_admin_password_if_required + Page::Main::Menu.perform(&:has_personal_area?) end @@ -105,6 +102,24 @@ module QA Page::Main::Menu.perform(&:signed_in?) end + # Handle request for password change + # Happens on clean GDK installations when seeded root admin password is expired + # + def set_up_new_password_if_required(user:, skip_page_validation:) + return unless has_content?('Set up new password') + + Profile::Password.perform do |new_password_page| + password = user&.password || Runtime::User.password + new_password_page.set_new_password(password, password) + end + + sign_in_using_credentials(user: user, skip_page_validation: skip_page_validation) + end + + def set_up_new_admin_password_if_required + set_up_new_password_if_required(user: admin, skip_page_validation: false) + end + def self.path '/users/sign_in' end @@ -181,6 +196,13 @@ module QA private + def admin + @admin ||= QA::Resource::User.init do |user| + user.username = QA::Runtime::User.admin_username + user.password = QA::Runtime::User.admin_password + end + end + def sign_in_using_gitlab_credentials(user:, skip_page_validation: false) wait_if_retry_later @@ -219,20 +241,6 @@ module QA fill_element :password_field, user.password end - # Handle request for password change - # Happens on clean GDK installations when seeded root admin password is expired - # - def set_up_new_password_if_required(user:, skip_page_validation:) - return unless has_content?('Set up new password') - - Profile::Password.perform do |new_password_page| - password = user&.password || Runtime::User.password - new_password_page.set_new_password(password, password) - end - - sign_in_using_credentials(user: user, skip_page_validation: skip_page_validation) - end - def set_initial_password_if_present return unless has_content?('Change your password') diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index dc2f908a906..a1d91621090 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -10,7 +10,10 @@ module QA view 'app/views/projects/merge_requests/creations/_new_compare.html.haml' do element :compare_branches_button - element :source_branch_dropdown + end + + view 'app/assets/javascripts/merge_requests/components/compare_dropdown.vue' do + element :source_branch_dropdown, ':data-qa-selector="qaSelector"' # rubocop:disable QA/ElementWithPattern end view 'app/views/projects/merge_requests/_page.html.haml' do @@ -46,8 +49,7 @@ module QA def select_source_branch(branch) click_element(:source_branch_dropdown) - fill_element(:dropdown_input_field, branch) - click_via_capybara(:click_on, branch) + search_and_select(branch) end end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 6fd48692730..df0c0ec4202 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -11,11 +11,6 @@ module QA element :review_preview_dropdown end - # Remove once :mr_review_submit_comment ff is enabled by default - view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do - element :submit_review_button - end - view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do element :review_bar_content end @@ -170,16 +165,8 @@ module QA click_element(:review_preview_dropdown) end - # Remove if statement once :mr_review_submit_comment ff is enabled by default - - if has_element?(:submit_review_dropdown, wait: 5) - click_element(:submit_review_dropdown) - click_element(:submit_review_button) - else - within_element(:review_bar_content) do - click_element(:submit_review_button) - end - end + click_element(:submit_review_dropdown) + click_element(:submit_review_button) # After clicking the button, wait for the review bar to disappear # before moving on to the next part of the test diff --git a/qa/qa/page/profile/chat_names/new.rb b/qa/qa/page/profile/chat_names/new.rb new file mode 100644 index 00000000000..351d36b87d5 --- /dev/null +++ b/qa/qa/page/profile/chat_names/new.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Page + module Profile + module ChatNames + class New < Chemlab::Page + button :authorize, value: /Authorize/i + end + end + end + end +end diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index c48b1a67d90..bb1095371a8 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -5,8 +5,6 @@ module QA module Project module Import class Github < Page::Base - include Page::Component::Select2 - view 'app/views/import/github/new.html.haml' do element :personal_access_token_field element :authenticate_button @@ -17,7 +15,7 @@ module QA element :project_path_field element :import_button element :project_path_content - element :go_to_project_button + element :go_to_project_link element :import_status_indicator end @@ -62,9 +60,9 @@ module QA # # @param [String] gh_project_name # @return [Boolean] - def has_go_to_project_button?(gh_project_name) + def has_go_to_project_link?(gh_project_name) within_element(:project_import_row, source_project: gh_project_name) do - has_element?(:go_to_project_button) + has_element?(:go_to_project_link) end end diff --git a/qa/qa/page/project/members.rb b/qa/qa/page/project/members.rb index 4692f3621b8..463e3ca6fca 100644 --- a/qa/qa/page/project/members.rb +++ b/qa/qa/page/project/members.rb @@ -4,49 +4,8 @@ module QA module Page module Project class Members < Page::Base - include QA::Page::Component::InviteMembersModal - include QA::Page::Component::MembersFilter - - view 'app/assets/javascripts/members/components/members_tabs.vue' do - element :groups_list_tab - end - - view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do - element :invite_a_group_button - end - - view 'app/assets/javascripts/invite_members/constants.js' do - element :invite_members_button - end - - view 'app/assets/javascripts/pages/projects/project_members/index.js' do - element :group_row - end - - view 'app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue' do - element :delete_group_access_link - end - - view 'app/assets/javascripts/members/components/modals/remove_group_link_modal.vue' do - element :remove_group_link_modal_content - end - - def remove_group(group_name) - click_element :groups_list_tab - - within_element(:group_row, text: group_name) do - click_element :delete_group_access_link - end - - within_element(:remove_group_link_modal_content) do - click_button 'Remove group' - end - end - - def has_group?(group_name) - click_element :groups_list_tab - has_element?(:group_row, text: group_name) - end + include Page::Component::Members::InviteMembersModal + include Page::Component::Members::MembersTable end end end diff --git a/qa/qa/page/project/monitor/alerts/index.rb b/qa/qa/page/project/monitor/alerts/index.rb index 50b69d59db7..1363fb32498 100644 --- a/qa/qa/page/project/monitor/alerts/index.rb +++ b/qa/qa/page/project/monitor/alerts/index.rb @@ -11,7 +11,7 @@ module QA end def has_alert_with_title?(title) - has_link?(title) + has_link?(title, wait: 5) end end end diff --git a/qa/qa/page/project/monitor/incidents/index.rb b/qa/qa/page/project/monitor/incidents/index.rb index 9317cb27562..1b30e484723 100644 --- a/qa/qa/page/project/monitor/incidents/index.rb +++ b/qa/qa/page/project/monitor/incidents/index.rb @@ -8,11 +8,16 @@ module QA class Index < Page::Base view 'app/assets/javascripts/incidents/components/incidents_list.vue' do element :create_incident_button + element :incident_link end def create_incident click_element :create_incident_button end + + def has_incident?(wait: Support::Repeater::DEFAULT_MAX_WAIT_TIME) + wait_until(max_duration: wait) { has_element?(:incident_link) } + end end end end diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb index 20999f7c92a..493ec08d023 100644 --- a/qa/qa/page/project/secure/configuration_form.rb +++ b/qa/qa/page/project/secure/configuration_form.rb @@ -5,7 +5,6 @@ module QA module Project module Secure class ConfigurationForm < QA::Page::Base - include QA::Page::Component::Select2 include QA::Page::Settings::Common view 'app/assets/javascripts/security_configuration/components/app.vue' do diff --git a/qa/qa/page/project/settings/alerts.rb b/qa/qa/page/project/settings/alerts.rb index 7b1d738ec3c..901a668f082 100644 --- a/qa/qa/page/project/settings/alerts.rb +++ b/qa/qa/page/project/settings/alerts.rb @@ -10,6 +10,7 @@ module QA element :incident_templates_dropdown element :save_changes_button element :incident_templates_item + element :enable_email_notification_checkbox end view 'app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue' do @@ -26,8 +27,16 @@ module QA element :prometheus_url_field end + def go_to_alert_settings + click_link_with_text('Alert settings') + end + def enable_incident_for_alert - check_element(:create_incident_checkbox) + check_element(:create_incident_checkbox, true) + end + + def enable_email_notification + check_element(:enable_email_notification_checkbox, true) end def select_issue_template(template) @@ -37,7 +46,7 @@ module QA end end - def save_incident_settings + def save_alert_settings click_element :save_changes_button end diff --git a/qa/qa/page/project/settings/default_branch.rb b/qa/qa/page/project/settings/default_branch.rb index 69ac45ce72d..a59158966c1 100644 --- a/qa/qa/page/project/settings/default_branch.rb +++ b/qa/qa/page/project/settings/default_branch.rb @@ -5,6 +5,8 @@ module QA module Project module Settings class DefaultBranch < Page::Base + include ::QA::Page::Component::Dropdown + view 'app/views/projects/branch_defaults/_show.html.haml' do element :save_changes_button end @@ -13,14 +15,9 @@ module QA element :default_branch_dropdown end - view 'app/assets/javascripts/ref/components/ref_selector.vue' do - element :ref_selector_searchbox - end - def set_default_branch(branch) - find_element(:default_branch_dropdown, visible: false).click - find_element(:ref_selector_searchbox, visible: false).fill_in(with: branch) - click_button branch + expand_select_list + search_and_select(branch) end def click_save_changes_button diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb index 58b3badbb22..c97593312d1 100644 --- a/qa/qa/page/project/settings/integrations.rb +++ b/qa/qa/page/project/settings/integrations.rb @@ -10,6 +10,7 @@ module QA element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern + element :gitlab_slack_application_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern end def click_on_prometheus_integration @@ -27,6 +28,10 @@ module QA def click_jenkins_ci_link click_element :jenkins_link end + + def click_slack_application_link + click_element :gitlab_slack_application_link + end end end end diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index ca5d13abdae..8b9b72758d8 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -6,7 +6,6 @@ module QA module Settings class Main < Page::Base include QA::Page::Settings::Common - include Component::Select2 include SubMenus::Project include Component::Breadcrumbs include Layout::Flash diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index ae6a04028c9..c42a4e0bebe 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -16,7 +16,7 @@ module QA end view 'app/views/projects/_merge_request_pipelines_and_threads_options.html.haml' do - element :allow_merge_if_all_discussions_are_resolved_checkbox + element :only_allow_merge_if_all_discussions_are_resolved_checkbox end def click_save_changes @@ -29,7 +29,7 @@ module QA end def enable_merge_if_all_disscussions_are_resolved - check_element(:allow_merge_if_all_discussions_are_resolved_checkbox, true) + check_element(:only_allow_merge_if_all_discussions_are_resolved_checkbox, true) click_save_changes end end diff --git a/qa/qa/page/registration/welcome.rb b/qa/qa/page/registration/welcome.rb index 660b33b4f5b..77cdbe8fd9e 100644 --- a/qa/qa/page/registration/welcome.rb +++ b/qa/qa/page/registration/welcome.rb @@ -21,6 +21,10 @@ module QA # Only implemented in EE end + def choose_create_a_new_project_if_available + # Only implemented in EE + end + def click_get_started_button Support::Retrier.retry_until do click_element :get_started_button diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb index 3f7aa837d3c..769f8accfca 100644 --- a/qa/qa/page/search/results.rb +++ b/qa/qa/page/search/results.rb @@ -4,9 +4,9 @@ module QA module Page module Search class Results < QA::Page::Base - view 'app/views/search/_category.html.haml' do - element :code_tab - element :projects_tab + view 'app/assets/javascripts/search/sidebar/components/scope_navigation.vue' do + element :code_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern + element :projects_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern end view 'app/views/search/results/_blob_data.html.haml' do diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 44520b04e9a..d7a220bc83f 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -19,6 +19,7 @@ module QA (respond_to?(:api_put_path) && respond_to?(:api_put_body)) end + # @return [String] the resource web url def fabricate_via_api! unless api_support? raise NotImplementedError, "Resource #{self.class.name} does not support fabrication via the API!" @@ -122,9 +123,10 @@ module QA process_api_response(api_post_to(api_post_path, api_post_body)) end - def api_post_to(post_path, post_body) + def api_post_to(post_path, post_body, args = {}) if post_path == "/graphql" - graphql_response = post(Runtime::API::Request.new(api_client, post_path).url, query: post_body) + payload = post_body.is_a?(String) ? { query: post_body } : post_body + graphql_response = post(Runtime::API::Request.new(api_client, post_path).url, payload, args) body = flatten_hash(parse_body(graphql_response)) @@ -137,9 +139,9 @@ module QA body[:id] = body.fetch(:id).split('/').last if body.key?(:id) - body.transform_keys { |key| key.to_s.underscore.to_sym } + body.deep_transform_keys { |key| key.to_s.underscore.to_sym } else - response = post(Runtime::API::Request.new(api_client, post_path).url, post_body) + response = post(Runtime::API::Request.new(api_client, post_path).url, post_body, args) unless response.code == HTTP_STATUS_CREATED raise( diff --git a/qa/qa/resource/design.rb b/qa/qa/resource/design.rb index 4a3214f3c0e..127e7ae61e3 100644 --- a/qa/qa/resource/design.rb +++ b/qa/qa/resource/design.rb @@ -8,9 +8,9 @@ module QA end attributes :id, - :filename, - :full_path, - :image + :filename, + :full_path, + :image def initialize @update = false @@ -60,16 +60,58 @@ module QA # # @return [String] def api_post_body - # TODO: design creation requires file upload via multipart/form-data request type with file passed in mutation - # which currently isn't supported by our api implementation - # https://gitlab.com/gitlab-org/gitlab/-/issues/366592 - raise NotImplementedError, "File uploads are not supported" + query = <<~GQL + mutation ($files: [Upload!]!, $projectPath: ID!, $iid: ID!) { + designManagementUpload(input: { files: $files, projectPath: $projectPath, iid: $iid }) { + designs { + id + fullPath + image + filename + webUrl + } + } + } + GQL + operations = { + query: query, + variables: { + files: nil, + projectPath: issue.project.full_path, + iid: issue.iid + } + } + + { + operations: JSON.dump(operations), + map: '{"0":["variables.files"]}', + "0": ::File.new(filepath) + } + end + + # Override api_post_to method to add multipart request option + # + # @param [String] post_path + # @param [Hash] post_body + # @param [Hash] args + # @return [Hash] + def api_post_to(post_path, post_body, args = {}) + super(post_path, post_body, { content_type: 'multipart/form-data' }) + end + + # Return first design from fabricated design array + # designManagementUpload mutation doesn't support returning single design + # + # @param [Hash] api_resource + # @return [Hash] + def transform_api_resource(api_resource) + api_resource.key?(:designs) ? api_resource[:designs].first : api_resource end private def filepath - ::File.absolute_path(::File.join('qa', 'fixtures', 'designs', filename)) + ::File.join(Runtime::Path.fixtures_path, 'designs', filename) end end end diff --git a/qa/qa/resource/import_project.rb b/qa/qa/resource/import_project.rb index 105d75285f1..a490905cbd6 100644 --- a/qa/qa/resource/import_project.rb +++ b/qa/qa/resource/import_project.rb @@ -7,7 +7,7 @@ module QA def initialize @name = "ImportedProject-#{SecureRandom.hex(8)}" - @file_path = ::File.join('qa', 'fixtures', 'export.tar.gz') + @file_path = ::File.join(Runtime::Path.fixtures_path, 'export.tar.gz') end def fabricate! diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index 4bf9c2bed6b..7f31808d2ff 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -24,7 +24,15 @@ module QA end def list_members - JSON.parse(get(Runtime::API::Request.new(api_client, api_members_path).url).body) + parse_body(api_get_from(api_members_path)) + end + + def list_all_members + parse_body(api_get_from("#{api_members_path}/all")) + end + + def find_member(username) + list_members.find { |member| member[:username] == username } end def invite_group(group, access_level = AccessLevel::GUEST) diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb index 910065d76a8..7d5036c5cf4 100644 --- a/qa/qa/resource/pipeline.rb +++ b/qa/qa/resource/pipeline.rb @@ -10,9 +10,9 @@ module QA end attributes :id, - :status, - :ref, - :sha + :status, + :ref, + :sha # array in form # [ @@ -49,6 +49,10 @@ module QA "/projects/#{project.id}/pipelines/#{id}" end + def api_pipeline_jobs_path + "#{api_get_path}/jobs" + end + def api_post_path "/projects/#{project.id}/pipeline" end @@ -93,6 +97,10 @@ module QA result[:downstream_pipeline][:id] end + + def pipeline_jobs + parse_body(api_get_from(api_pipeline_jobs_path)) + end end end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 3f42c6b649e..019617325e0 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -198,6 +198,10 @@ module QA "#{api_get_path}/pipelines" end + def api_latest_pipeline_path + "#{api_pipelines_path}/latest" + end + def api_pipeline_schedules_path "#{api_get_path}/pipeline_schedules" end @@ -400,6 +404,10 @@ module QA auto_paginated_response(request_url(api_pipelines_path, per_page: '100'), attempts: attempts) end + def latest_pipeline + parse_body(api_get_from(api_latest_pipeline_path)) + end + def jobs response = get(request_url(api_jobs_path)) parse_body(response) @@ -450,7 +458,7 @@ module QA parse_body(response) end - # Fetch project specific runners + # Fetch project runners # # @param [Hash] **kwargs optional query arguments, see: https://docs.gitlab.com/ee/api/runners.html#list-projects-runners # @return [Array] diff --git a/qa/qa/resource/project_web_hook.rb b/qa/qa/resource/project_web_hook.rb index 8b806c42030..86e662932e1 100644 --- a/qa/qa/resource/project_web_hook.rb +++ b/qa/qa/resource/project_web_hook.rb @@ -16,7 +16,11 @@ module QA confidential_note ].freeze - attr_accessor :url, :enable_ssl, :id + attr_accessor :url, :enable_ssl + + attribute :disabled_until + attribute :id + attribute :alert_status attribute :project do Project.fabricate_via_api! do |resource| @@ -33,19 +37,28 @@ module QA def initialize @id = nil @enable_ssl = false + @alert_status = nil @url = nil end + def fabricate_via_api! + resource_web_url = super + + @id = api_response[:id] + + resource_web_url + end + def resource_web_url(resource) "/project/#{project.name}/~/hooks/##{resource[:id]}/edit" end def api_get_path - "/projects/#{project.id}/hooks" + "#{api_post_path}/#{api_response[:id]}" end def api_post_path - api_get_path + "/projects/#{project.id}/hooks" end def api_post_body diff --git a/qa/qa/resource/runner_base.rb b/qa/qa/resource/runner_base.rb index 7580aa108c9..9386a5d9765 100644 --- a/qa/qa/resource/runner_base.rb +++ b/qa/qa/resource/runner_base.rb @@ -122,7 +122,7 @@ module QA end def runner(**kwargs) - raise("Not implemented!") + raise NotImplementedError end end end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 0398509396f..16d0f6ad380 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -12,7 +12,8 @@ module QA :extern_uid, :expect_fabrication_success, :hard_delete_on_api_removal, - :access_level + :access_level, + :email_domain attributes :id, :name, @@ -25,6 +26,7 @@ module QA @hard_delete_on_api_removal = false @unique_id = SecureRandom.hex(8) @expect_fabrication_success = true + @email_domain = 'example.com' end def self.default @@ -63,7 +65,7 @@ module QA def email @email ||= begin api_email = api_resource&.dig(:email) - api_email && !api_email.empty? ? api_email : "#{username}@example.com" + api_email && !api_email.empty? ? api_email : "#{username}@#{email_domain}" end end diff --git a/qa/qa/runtime/application_settings.rb b/qa/qa/runtime/application_settings.rb index 5aeab922a12..53ed6a9266b 100644 --- a/qa/qa/runtime/application_settings.rb +++ b/qa/qa/runtime/application_settings.rb @@ -31,8 +31,8 @@ module QA # TODO: This class probably needs to be refactored because this method relies on original settings to have been # populated sometime in the past and there is no guarantee original settings instance variable is still valid - def restore_application_settings(*application_settings_keys) - set_application_settings(**@original_application_settings.slice(*application_settings_keys)) + def restore_application_settings(...) + set_application_settings(**@original_application_settings.slice(...)) end private diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index faf2023f7c2..f01657c8deb 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -104,7 +104,7 @@ module QA # Specify the user-agent to allow challenges to be bypassed # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938 - if QA::Runtime::Env.user_agent + unless QA::Runtime::Env.user_agent.blank? capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}" end @@ -117,6 +117,38 @@ module QA capabilities['goog:chromeOptions'][:args] << 'window-size=1480,2200' end + # Slack tries to open an external URL handler + # The test needs to default the handler to always open Slack + # to prevent a blocking popup. + if QA::Runtime::Env.slack_workspace + slack_default_preference = { + 'protocol_handler' => { + 'allowed_origin_protocol_pairs' => { + "https://#{QA::Runtime::Env.slack_workspace}.slack.com" => { + 'slack' => true + } + } + } + } + + default_profile = File.join("#{chrome_profile_location}/Default") + FileUtils.mkdir_p(default_profile) unless Dir.exist?(default_profile) + preferences = slack_default_preference + + # mutate the preferences if it exists + # else write a new file + if File.exist?("#{default_profile}/Preferences") + begin + preferences = JSON.parse(File.read("#{default_profile}/Preferences")) + preferences.deep_merge!(slack_default_preference) + rescue JSON::ParserError => _ + end + end + + File.write("#{default_profile}/Preferences", preferences.to_json) + append_chrome_profile_to_capabilities(capabilities) + end + when :safari if QA::Runtime::Env.remote_mobile_device_name capabilities['platformName'] = 'iOS' @@ -131,10 +163,7 @@ module QA # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true. # Useful to speed up local QA. - if QA::Runtime::Env.reuse_chrome_profile? - qa_profile_dir = ::File.expand_path('../../tmp/qa-profile', __dir__) - capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{qa_profile_dir}" - end + append_chrome_profile_to_capabilities(capabilities) if QA::Runtime::Env.reuse_chrome_profile? selenium_options = { browser: QA::Runtime::Env.browser, @@ -193,6 +222,16 @@ module QA end # rubocop: enable Metrics/AbcSize + def self.append_chrome_profile_to_capabilities(capabilities) + return if capabilities['goog:chromeOptions'][:args].include?(chrome_profile_location) + + capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{chrome_profile_location}" + end + + def self.chrome_profile_location + ::File.expand_path('../../tmp/qa-profile', __dir__) + end + class Session include Capybara::DSL diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index e952c0337f2..b53c2320537 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -293,6 +293,18 @@ module QA ENV['JIRA_HOSTNAME'] end + def slack_workspace + ENV['QA_SLACK_WORKSPACE'] + end + + def slack_email + ENV['QA_SLACK_EMAIL'] + end + + def slack_password + ENV['QA_SLACK_PASSWORD'] + end + def jenkins_admin_username ENV.fetch('QA_JENKINS_USER', 'administrator') end @@ -502,6 +514,15 @@ module QA ENV['DEFAULT_CHROME_DOWNLOAD_PATH'] || Dir.tmpdir end + def require_slack_env! + missing_env = %i[slack_workspace slack_email slack_password].select do |method| + ::QA::Runtime::Env.public_send(method).nil? + end + return unless missing_env.any? + + raise "Missing Slack env: #{missing_env.map(&:upcase).join(', ')}" + end + private def remote_grid_credentials diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb index d56af9b4872..201cf7f0fcb 100644 --- a/qa/qa/runtime/fixtures.rb +++ b/qa/qa/runtime/fixtures.rb @@ -14,7 +14,8 @@ module QA response = get(request.url) unless response.code == HTTP_STATUS_OK - raise TemplateNotFoundError, "Template at #{request.mask_url} could not be found (#{response.code}): `#{response}`." + raise TemplateNotFoundError, + "Template at #{request.mask_url} could not be found (#{response.code}): `#{response}`." end parse_body(response)[:content] @@ -34,11 +35,7 @@ module QA end def read_fixture(fixture_path, file_name) - file_path = Pathname - .new(__dir__) - .join("../fixtures/#{fixture_path}/#{file_name}") - - File.read(file_path) + File.read(File.join(Runtime::Path.fixtures_path, fixture_path, file_name)) end private diff --git a/qa/qa/runtime/gpg.rb b/qa/qa/runtime/gpg.rb index 9f8baf7e580..3314b14cd68 100644 --- a/qa/qa/runtime/gpg.rb +++ b/qa/qa/runtime/gpg.rb @@ -3,34 +3,19 @@ module QA module Runtime class GPG - attr_reader :key, :key_id + attr_reader :key_id + + delegate :shell, to: 'QA::Service::Shellout' def initialize @key_id = 'B8358D73048DACC4' - import_key(File.expand_path('qa/ee/fixtures/gpg/admin.asc')) - @key = collect_key.first - end - - private - - def import_key(path) - import_key = "gpg --import #{path}" - execute(import_key) - end - - def collect_key - get_ascii_format = "gpg --armor --export #{@key_id}" - execute(get_ascii_format) end - def execute(command) - Open3.capture2e(*command) do |stdin, out, wait| - out.each_char { |char| print char } + def key + return @key if defined?(@key) - if wait.value.exited? && wait.value.exitstatus.nonzero? - raise CommandError, "Command `#{command}` failed!" - end - end + shell("gpg --import #{File.join(Path.fixtures_path, 'gpg', 'admin.asc')}") + @key = shell("gpg --armor --export #{key_id}") end end end diff --git a/qa/qa/scenario/test/integration/gitaly_cluster.rb b/qa/qa/scenario/test/integration/gitaly_cluster.rb new file mode 100644 index 00000000000..66b7485a8dc --- /dev/null +++ b/qa/qa/scenario/test/integration/gitaly_cluster.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class GitalyCluster < Test::Instance::All + tags :gitaly_cluster + end + end + end + end +end diff --git a/qa/qa/scenario/test/instance/integrations.rb b/qa/qa/scenario/test/integration/integrations.rb index b943b95c51e..0797f5d36c7 100644 --- a/qa/qa/scenario/test/instance/integrations.rb +++ b/qa/qa/scenario/test/integration/integrations.rb @@ -3,8 +3,8 @@ module QA module Scenario module Test - module Instance - class Integrations < All + module Integration + class Integrations < Test::Instance::All tags :integrations end end diff --git a/qa/qa/scenario/test/instance/jira.rb b/qa/qa/scenario/test/integration/jira.rb index 784a71515cb..dda804f0fad 100644 --- a/qa/qa/scenario/test/instance/jira.rb +++ b/qa/qa/scenario/test/integration/jira.rb @@ -3,8 +3,8 @@ module QA module Scenario module Test - module Instance - class Jira < All + module Integration + class Jira < Test::Instance::All tags :jira end end diff --git a/qa/qa/scenario/test/integration/mtls.rb b/qa/qa/scenario/test/integration/mtls.rb new file mode 100644 index 00000000000..6c4a035b0f2 --- /dev/null +++ b/qa/qa/scenario/test/integration/mtls.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class Mtls < Test::Instance::All + tags :mtls + end + end + end + end +end diff --git a/qa/qa/service/cluster_provider/gcloud.rb b/qa/qa/service/cluster_provider/gcloud.rb index 14c13eecb8d..749ebca8897 100644 --- a/qa/qa/service/cluster_provider/gcloud.rb +++ b/qa/qa/service/cluster_provider/gcloud.rb @@ -55,7 +55,7 @@ module QA shell <<~CMD.tr("\n", ' ') curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && chmod 700 get_helm.sh && - ./get_helm.sh + DESIRED_VERSION=v3.7.0 ./get_helm.sh CMD end diff --git a/qa/qa/service/docker_run/ldap.rb b/qa/qa/service/docker_run/ldap.rb index c33d75ff640..a6982bddf28 100644 --- a/qa/qa/service/docker_run/ldap.rb +++ b/qa/qa/service/docker_run/ldap.rb @@ -28,7 +28,7 @@ module QA if volume_exists?(volume_name) volume_name else - File.expand_path("../fixtures/ldap/#{volume_name}", __dir__) + ::File.join(Runtime::Path.fixtures_path, 'ldap', volume_name) end end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 9b93297f6ff..218d5eecc85 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -13,6 +13,7 @@ module QA def shell(command, stdin_data: nil, fail_on_exception: true, stream_progress: true, mask_secrets: []) # rubocop:disable Metrics/CyclomaticComplexity cmd_string = Array(command).join(' ') + cmd_output = '' QA::Runtime::Logger.info("Executing: `#{mask_secrets_on_string(cmd_string, mask_secrets).cyan}`") @@ -21,7 +22,6 @@ module QA stdin.close if stdin_data print_progress_dots = stream_progress && !Runtime::Env.running_in_ci? - cmd_output = '' out.each do |line| line = mask_secrets_on_string(line, mask_secrets) @@ -43,6 +43,8 @@ module QA Runtime::Logger.debug("Command output:\n#{cmd_output.strip}") unless cmd_output.empty? end + + cmd_output.strip end def sql_to_docker_exec_cmd(sql, username, password, database, host, container) 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 a10e95a860c..9017aba8e4a 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 @@ -3,7 +3,10 @@ module QA # https://github.com/gitlab-qa-github/import-test <- project under test # - RSpec.describe 'Manage', product_group: :import do + RSpec.describe 'Manage', product_group: :import, quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391228', + type: :waiting_on + } do describe 'GitHub import' do include_context 'with github import' diff --git a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb index fb530967073..8439b881ed7 100644 --- a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb +++ b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb @@ -125,11 +125,48 @@ module QA end end + context 'when hook fails' do + let(:fail_mock) do + <<~YAML + - request: + method: POST + path: /default + response: + status: 404 + headers: + Content-Type: text/plain + body: 'webhook failed' + YAML + end + + let(:hook_trigger_times) { 5 } + let(:disabled_after) { 4 } + + it 'hook is auto-disabled', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389595' do + setup_webhook(fail_mock, issues: true) do |webhook, smocker| + hook_trigger_times.times do + Resource::Issue.fabricate_via_api! do |issue_init| + issue_init.project = webhook.project + end + end + + expect { smocker.history(session).size }.to eventually_eq(disabled_after) + .within(max_duration: 30, sleep_interval: 2), + -> { "Should have #{disabled_after} events, got: #{smocker.history(session).size}" } + + webhook.reload! + + expect(webhook.alert_status).to eql('disabled') + end + end + end + private - def setup_webhook(**event_args) + def setup_webhook(mock = Vendor::Smocker::SmockerApi::DEFAULT_MOCK, **event_args) Service::DockerRun::Smocker.init(wait: 10) do |smocker| - smocker.register(session: session) + smocker.register(mock, session: session) webhook = Resource::ProjectWebHook.fabricate_via_api! do |hook| hook.url = smocker.url 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 052e3d0e32d..2dcbbadb4aa 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 @@ -78,18 +78,12 @@ module QA end end - # 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 + context "with designs" do let!(:source_design) do - Flow::Login.sign_in(as: user) - - Resource::Design.fabricate_via_browser_ui! do |design| - design.api_client = api_client + Resource::Design.fabricate! do |design| + design.api_client = source_admin_api_client design.issue = source_issue - end.reload! + end end let(:imported_design) do 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 7fe11c3bafe..42793406e6c 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 @@ -19,11 +19,11 @@ module QA end let(:imported_group_member) do - imported_group.reload!.list_members.find { |usr| usr['username'] == target_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'] == target_member.username } + imported_project.reload!.list_members.find { |usr| usr[:username] == target_member.username } end context 'with group member' do @@ -39,9 +39,7 @@ module QA aggregate_failures do expect(imported_project_member).to be_nil - expect(imported_group_member&.fetch('access_level')).to eq( - Resource::Members::AccessLevel::DEVELOPER - ) + expect(imported_group_member&.fetch(:access_level)).to eq(Resource::Members::AccessLevel::DEVELOPER) end end end @@ -59,9 +57,7 @@ module QA aggregate_failures do expect(imported_group_member).to be_nil - expect(imported_project_member&.fetch('access_level')).to eq( - Resource::Members::AccessLevel::DEVELOPER - ) + expect(imported_project_member&.fetch(:access_level)).to eq(Resource::Members::AccessLevel::DEVELOPER) end end 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 9f6b1d38bd7..60ece89844d 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 @@ -24,7 +24,7 @@ module QA 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') + group.avatar = File.new(File.join(Runtime::Path.fixtures_path, 'designs', 'tanuki.jpg'), 'r') end end 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 ad1abe6e5e9..124b6c9cd44 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 @@ -5,9 +5,16 @@ module QA describe 'User', :requires_admin, product_group: :organization do let(:admin_api_client) { Runtime::API::Client.as_admin } + let!(:parent_group) do + QA::Resource::Group.fabricate_via_api! do |group| + group.path = "parent-group-to-test-user-access-#{SecureRandom.hex(8)}" + end + end + let!(:sub_group) do QA::Resource::Group.fabricate_via_api! do |group| group.path = "sub-group-to-test-user-access-#{SecureRandom.hex(8)}" + group.sandbox = parent_group end end @@ -31,7 +38,7 @@ module QA end before do - sub_group.sandbox.add_member(parent_group_user) + parent_group.add_member(parent_group_user) end it( @@ -89,16 +96,14 @@ module QA after do parent_group_user.remove_via_api! - sub_group_project.remove_via_api! - sub_group.remove_via_api! end end context 'when added to sub-group' do let!(:parent_group_project) do Resource::Project.fabricate_via_api! do |project| - project.group = sub_group.sandbox - project.name = "sub-group-project-to-test-user-access" + project.group = parent_group + project.name = "parent-group-project-to-test-user-access" project.initialize_with_readme = true end end @@ -170,8 +175,6 @@ module QA after do sub_group_user.remove_via_api! - parent_group_project.remove_via_api! - sub_group.remove_via_api! end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/group_member_access_request_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/group_member_access_request_spec.rb new file mode 100644 index 00000000000..3a84646977f --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/group/group_member_access_request_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage', :requires_admin, product_group: :organization do + describe 'Group member access request' do + let!(:admin_api_client) { Runtime::API::Client.as_admin } + + let!(:user) do + Resource::User.fabricate_via_api! do |user| + user.api_client = admin_api_client + end + end + + let!(:group) do + Resource::Group.fabricate_via_api! do |group| + group.path = "group-for-access-request-#{SecureRandom.hex(8)}" + group.api_client = admin_api_client + end + end + + before do + Flow::Login.sign_in(as: user) + group.visit! + + Page::Group::Show.perform(&:click_request_access) + + Flow::Login.sign_in_as_admin + + Page::Main::Menu.perform do |menu| + menu.go_to_page_by_shortcut(:todos_shortcut_button) + end + + Page::Dashboard::Todos.perform do |todos| + todos.filter_todos_by_group(group) + end + end + + after do + user&.remove_via_api! + end + + it 'generates a todo item for the group owner', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370132' do + Page::Dashboard::Todos.perform do |todos| + expect(todos).to have_latest_todo_with_author( + author: user.name, + action: "has requested access to group #{group.path}" + ) + end + end + + context 'when managing requests as the group owner' do + before do + Page::Dashboard::Todos.perform do |todos| + todos.click_todo_with_content(group.name) + end + end + + context 'and request is accepted' do + before do + Page::Group::Members.perform do |members| + members.approve_access_request(user.username) + end + end + + it 'adds user to the group', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/386792' do + found_member = group.reload!.find_member(user.username) + + expect(found_member).not_to be_nil + expect(found_member.fetch(:access_level)) + .to eq(Resource::Members::AccessLevel::DEVELOPER) + end + end + + context 'and request is denied' do + before do + Page::Group::Members.perform do |members| + members.deny_access_request(user.username) + end + end + + it 'does not add user to the group', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/386793' do + found_member = group.reload!.find_member(user.username) + + expect(found_member).to be_nil + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb index 43a8af93e27..461928cbf1f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', product_group: :import do + RSpec.describe 'Manage', product_group: :import, quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391230', + type: :waiting_on + } do describe 'GitHub import' do include_context 'with github import' @@ -49,9 +52,9 @@ module QA aggregate_failures do expect(import_page).to have_imported_project(github_repo, wait: 240) - # validate button is present instead of navigating to avoid dealing with multiple tabs + # validate link is present instead of navigating to avoid dealing with multiple tabs # which makes the test more complicated - expect(import_page).to have_go_to_project_button(github_repo) + expect(import_page).to have_go_to_project_link(github_repo) end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index e0242008785..3f5842d756e 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -5,7 +5,9 @@ module QA it 'allows the user to register and login' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Resource::User.fabricate_via_browser_ui! + Resource::User.fabricate_via_browser_ui! do |user_resource| + user_resource.email_domain = 'gitlab.com' + end Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area @@ -65,8 +67,7 @@ module QA show.delete_account(user.password) end - # TODO: Remove retry_on_exception once https://gitlab.com/gitlab-org/gitlab/-/issues/24294 is resolved - Support::Waiter.wait_until(max_duration: 120, retry_on_exception: true, sleep_interval: 3) { !user.exists? } + Support::Waiter.wait_until(max_duration: 120, sleep_interval: 3) { !user.exists? } end it 'allows recreating with same credentials', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb index 9bb08cb66bc..c57900efe35 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb @@ -5,9 +5,16 @@ module QA describe 'User', :requires_admin, product_group: :organization do let(:admin_api_client) { Runtime::API::Client.as_admin } + let!(:parent_group) do + QA::Resource::Group.fabricate_via_api! do |group| + group.path = "parent-group-to-test-user-access-#{SecureRandom.hex(8)}" + end + end + let!(:sub_group) do QA::Resource::Group.fabricate_via_api! do |group| group.path = "sub-group-to-test-user-access-#{SecureRandom.hex(8)}" + group.sandbox = parent_group end end @@ -31,7 +38,7 @@ module QA end before do - sub_group.sandbox.add_member(parent_group_user) + parent_group.add_member(parent_group_user) end it( @@ -54,16 +61,14 @@ module QA after do parent_group_user.remove_via_api! - sub_group_project.remove_via_api! - sub_group.remove_via_api! end end context 'when added to sub-group' do let!(:parent_group_project) do Resource::Project.fabricate_via_api! do |project| - project.group = sub_group.sandbox - project.name = "sub-group-project-to-test-user-access" + project.group = parent_group + project.name = "parent-group-project-to-test-user-access" project.initialize_with_readme = true end end @@ -100,8 +105,6 @@ module QA after do sub_group_user.remove_via_api! - parent_group_project.remove_via_api! - sub_group.remove_via_api! 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 eaf43f04c4b..130006fe424 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 @@ -5,7 +5,7 @@ module QA describe 'Design Management' do let(:issue) { Resource::Issue.fabricate_via_api! } let(:design_filename) { 'banana_sample.gif' } - let(:design) { File.absolute_path(File.join('qa', 'fixtures', 'designs', design_filename)) } + let(:design) { File.join(Runtime::Path.fixtures_path, 'designs', design_filename) } let(:annotation) { "This design is great!" } before do @@ -13,7 +13,7 @@ module QA end it 'user adds a design and annotates it', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do issue.visit! Page::Project::Issue::Show.perform do |issue| diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb index b70590e65c8..4e9d74a5117 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb @@ -45,7 +45,7 @@ module QA mailhog_data = JSON.parse(mailhog_response.body) total = mailhog_data.dig('total') subjects = mailhog_data.dig('items') - .map(&method(:mailhog_item_subject)) + .map { |item| mailhog_item_subject(item) } Runtime::Logger.debug(%Q[Total number of emails: #{total}]) Runtime::Logger.debug(%Q[Subjects:\n#{subjects.join("\n")}]) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 45c3c264837..e2fd0ec9cef 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -60,7 +60,7 @@ module QA context 'when using attachments in comments', :object_storage do let(:png_file_name) { 'testfile.png' } let(:file_to_attach) do - File.absolute_path(File.join('qa', 'fixtures', 'designs', png_file_name)) + File.join(Runtime::Path.fixtures_path, 'designs', png_file_name) end before do diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb index 829b7bab829..7b89b021d89 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Create', :reliable, product_group: :code_review do - context 'Add batch suggestions to a Merge Request' do + context 'with merge request batch suggestions' do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'suggestions_project' @@ -15,9 +15,7 @@ module QA merge_request.title = 'Needs some suggestions' merge_request.description = '... so please add them.' merge_request.file_content = File.read( - Pathname - .new(__dir__) - .join('../../../../../../fixtures/metrics_dashboards/templating.yml') + File.join(Runtime::Path.fixtures_path, 'metrics_dashboards', 'templating.yml') ) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb index 433ef90d9aa..499d4c00384 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Create' do - context 'Add suggestions to a Merge Request', product_group: :code_review do + context 'with merge request suggestions', product_group: :code_review do let(:commit_message) { 'Applying suggested change for testing purposes.' } let(:project) do @@ -17,9 +17,7 @@ module QA merge_request.title = 'Needs some suggestions' merge_request.description = '... so please add them.' merge_request.file_content = File.read( - Pathname - .new(__dir__) - .join('../../../../../../fixtures/metrics_dashboards/templating.yml') + File.join(Runtime::Path.fixtures_path, 'metrics_dashboards', 'templating.yml') ) end end @@ -43,7 +41,8 @@ module QA merge_request.visit! end - it 'applies a single suggestion with a custom message', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347711' do + it 'applies a single suggestion with a custom message', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347711' do Page::MergeRequest::Show.perform do |merge_request| merge_request.click_diffs_tab merge_request.apply_suggestion_with_message(commit_message) diff --git a/qa/qa/specs/features/browser_ui/3_create/project_wiki/project_based_file_upload_spec.rb b/qa/qa/specs/features/browser_ui/3_create/project_wiki/project_based_file_upload_spec.rb index fbe662341c0..c782139823e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/project_wiki/project_based_file_upload_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/project_wiki/project_based_file_upload_spec.rb @@ -17,7 +17,7 @@ module QA end it 'by creating a formatted page with an image uploaded', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347640' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347640' do initial_wiki.visit! Page::Project::Wiki::Show.perform(&:click_new_page) @@ -26,7 +26,7 @@ module QA edit.set_title(page_title) edit.use_new_editor edit.add_heading('Heading 1', heading_text) - edit.upload_image(File.absolute_path(File.join('qa', 'fixtures', 'designs', image_file_name))) + edit.upload_image(File.join(Runtime::Path.fixtures_path, 'designs', image_file_name)) end Page::Project::Wiki::Edit.perform(&:click_submit) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb index 50df8afafaf..86a9f2c46fb 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb @@ -11,7 +11,7 @@ module QA shared_examples 'project license detection' do it 'displays the name of the license on the repository' do - license_path = File.expand_path("../../../../../fixtures/software_licenses/#{license_file_name}", __dir__) + license_path = File.join(Runtime::Path.fixtures_path, 'software_licenses', license_file_name) Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.add_files([{ file_path: 'LICENSE', content: File.read(license_path) }]) 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 8c171e6bc8c..080832990c9 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 @@ -2,14 +2,14 @@ module QA RSpec.describe 'Create', :skip_live_env, except: { job: 'review-qa-*' }, - feature_flag: { name: 'vscode_web_ide', scope: :global }, - product_group: :editor, - quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/387928', - type: :stale - } do + feature_flag: { name: 'vscode_web_ide', scope: :global }, + product_group: :editor, + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/387928', + type: :stale + } do describe 'Git Server Hooks' do - let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', 'README.md')) } + let(:file_path) { File.join(Runtime::Path.fixtures_path, 'web_ide', 'README.md') } let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -29,9 +29,9 @@ module QA Runtime::Feature.enable(:vscode_web_ide) end - context 'Custom error messages' do + context 'with 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 + 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 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 781623d10f6..b83a95694de 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,12 +1,11 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create', feature_flag: { name: 'vscode_web_ide', scope: :global }, product_group: :editor, quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/387032', - type: :stale - } do + RSpec.describe 'Create', product_group: :editor, + feature_flag: { name: 'vscode_web_ide', scope: :global }, + quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/387032', type: :stale } do describe 'Upload a file in Web IDE' do - let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', file_name)) } + let(:file_path) { File.join(Runtime::Path.fixtures_path, 'web_ide', file_name) } let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -43,7 +42,8 @@ module QA context 'when the file is a text file' do let(:file_name) { 'text_file.txt' } - it 'shows the Edit tab with the text', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347852' do + 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) 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 deleted file mode 100644 index 599c34ed3db..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/web_terminal_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe( - 'Create', - :runner, - # TODO: remove limitation to only run on main when the bug is fixed - only: { pipeline: :main }, - quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338179', - type: :bug - }, - feature_flag: { name: 'vscode_web_ide', scope: :global }, - product_group: :editor - ) do - describe 'Web IDE web terminal' do - before do - Runtime::Feature.disable(:vscode_web_ide) - project = Resource::Project.fabricate_via_api! do |project| - project.name = 'web-terminal-project' - end - - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = project - commit.commit_message = 'Add .gitlab/.gitlab-webide.yml' - commit.add_files( - [ - { - file_path: '.gitlab/.gitlab-webide.yml', - content: <<~YAML - terminal: - tags: ["web-ide"] - script: sleep 60 - YAML - } - ] - ) - end - - @runner = Resource::ProjectRunner.fabricate_via_api! do |runner| - runner.project = project - runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = %w[web-ide] - runner.image = 'gitlab/gitlab-runner:latest' - runner.config = <<~END - concurrent = 1 - - [session_server] - listen_address = "0.0.0.0:8093" - advertise_address = "localhost:8093" - session_timeout = 120 - END - end - - Flow::Login.sign_in - - project.visit! - end - - after do - Runtime::Feature.enable(:vscode_web_ide) - @runner.remove_via_api! if @runner - end - - it 'user starts the web terminal', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347737' do - Page::Project::Show.perform(&:open_web_ide!) - - # Start the web terminal and check that there were no errors - # The terminal screen is a canvas element, so we can't read its content, - # so we infer that it's working if: - # a) The terminal JS package has loaded, and - # b) It's not stuck in a "Loading/Starting" state, and - # c) There's no alert stating there was a problem - # d) There are no JS console errors - # - # The terminal itself is a third-party package so we assume it is - # adequately tested elsewhere. - # - # There are also FE specs - # * spec/frontend/ide/components/terminal/terminal_controls_spec.js - Page::Project::WebIDE::Edit.perform do |edit| - edit.wait_until_ide_loads - edit.start_web_terminal - - expect(edit).to have_no_alert - expect(edit).to have_finished_loading - expect(edit).to have_terminal_screen - end - - # 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 - - if errors.present? - raise("Console error(s):\n#{errors.join("\n\n")}") - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb index ddd91f97515..379499662c2 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/merge_mr_when_pipline_is_blocked_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Verify', :runner, product_group: :pipeline_execution do - context 'When pipeline is blocked' do + context 'when pipeline is blocked' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } let(:project) 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 deleted file mode 100644 index 5dff2db6f2b..00000000000 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/mr_event_rule_pipeline_spec.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring 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)}" } - - let(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'merge-request-only-job' - end - end - - let!(:runner) do - Resource::ProjectRunner.fabricate! do |runner| - runner.project = project - runner.name = executor - runner.tags = [executor] - end - end - - let!(:ci_file) do - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files( - [ - { - file_path: '.gitlab-ci.yml', - content: <<~YAML - #{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"' - YAML - } - ] - ) - end - end - - let(:merge_request) do - Resource::MergeRequest.fabricate_via_api! do |merge_request| - merge_request.project = project - merge_request.description = Faker::Lorem.sentence - merge_request.target_new_branch = false - merge_request.file_name = 'new.txt' - merge_request.file_content = Faker::Lorem.sentence - end - end - - before do - Flow::Login.sign_in - # 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! - end - - it 'only runs the job configured to run on merge requests', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347662' do - Page::Project::Pipeline::Show.perform do |pipeline| - aggregate_failures do - expect(pipeline).to have_job(mr_only_job_name) - expect(pipeline).to have_no_job(non_mr_only_job_name) - end - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb index 4294235a8b1..37d1e20111d 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb @@ -29,6 +29,9 @@ module QA { file_path: '.gitlab-ci.yml', content: <<~YAML + default: + tags: ["#{executor}"] + stages: - Stage1 - Stage2 @@ -36,26 +39,22 @@ module QA Prep: stage: Stage1 - tags: ["#{executor}"] script: exit 0 when: manual Build: stage: Stage2 - tags: ["#{executor}"] needs: ['Prep'] script: exit 0 parallel: 6 Test: stage: Stage3 - tags: ["#{executor}"] needs: ['Build'] script: exit 0 Deploy: stage: Stage3 - tags: ["#{executor}"] needs: ['Test'] script: exit 0 parallel: 6 @@ -67,6 +66,8 @@ module QA end before do + make_sure_to_have_a_skipped_pipeline + Flow::Login.sign_in project.visit! Flow::Pipeline.visit_latest_pipeline(status: 'skipped') @@ -84,7 +85,7 @@ module QA show.click_job_action('Prep') # Trigger pipeline manually show.wait_until(max_duration: 300, sleep_interval: 2, reload: false) do - project.pipelines.last[:status] == 'success' + project.latest_pipeline[:status] == 'success' end aggregate_failures do @@ -99,6 +100,50 @@ module QA end end end + + private + + # Wait for first pipeline to finish and have "skipped" status + # If it takes too long, create new pipeline and retry (2 times) + def make_sure_to_have_a_skipped_pipeline + attempts ||= 1 + Runtime::Logger.info('Waiting for pipeline to have status "skipped"...') + Support::Waiter.wait_until(max_duration: 120, sleep_interval: 3, retry_on_exception: true) do + project.latest_pipeline[:status] == 'skipped' + end + rescue Support::Repeater::WaitExceededError + raise 'Failed to create skipped pipeline after 3 attempts.' unless (attempts += 1) < 4 + + Runtime::Logger.debug( + "Previous pipeline took too long to finish. Potential jobs with problems:\n#{problematic_jobs}" + ) + Runtime::Logger.info("Triggering a new pipeline...") + trigger_new_pipeline + retry + end + + def trigger_new_pipeline + original_count = project.pipelines.length + Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = project + end + + Support::Waiter.wait_until(sleep_interval: 1) { project.pipelines.length > original_count } + end + + # We know that all the jobs in pipeline are purposely skipped + # The pipeline should have status "skipped" almost right away after being created + # If pipeline is held up, likely because there are some jobs that + # doesn't have either "skipped" or "manual" status + def problematic_jobs + pipeline = Resource::Pipeline.fabricate_via_api! do |pipeline| + pipeline.project = project + pipeline.id = project.latest_pipeline[:id] + end + + acceptable_statuses = %w[skipped manual] + pipeline.pipeline_jobs.select { |job| !(acceptable_statuses.include? job[:status]) } + end end end end diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb new file mode 100644 index 00000000000..fba2f22b3e5 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify', :runner, product_group: :runner do + describe 'Group runner registration' do + let(:executor) { "qa-runner-#{Time.now.to_i}" } + + let!(:runner) do + Resource::GroupRunner.fabricate! do |runner| + runner.name = executor + end + end + + after do + runner.remove_via_api! + end + + it( + 'user registers a new group runner', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/388740' + ) do + Flow::Login.sign_in + + runner.group.visit! + + Page::Group::Menu.perform(&:go_to_runners) + + expect(page).to have_content(executor) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index e141bac5e3f..4db935df43d 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -15,7 +15,7 @@ module QA runner.remove_via_api! end - it 'user registers a new specific runner', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348025' do + it 'user registers a new project runner', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348025' do Flow::Login.sign_in runner.project.visit! diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb index 800324afc58..ec07116550f 100644 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :skip_live_env, product_group: :container_registry do + RSpec.describe 'Package', :orchestrated, :skip_live_env, product_group: :container_registry, quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/390090', + type: :investigating + } do describe 'Self-managed Container Registry' do include Support::Helpers::MaskToken @@ -136,26 +139,30 @@ module QA Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([{ - file_path: '.gitlab-ci.yml', - content: - <<~YAML - build: - image: "#{docker_client_version}" - stage: build - services: - - name: "#{docker_client_version}-dind" - command: ["--insecure-registry=gitlab.test:5050"] - variables: - IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - script: - - docker login -u #{auth_user} -p #{auth_token} gitlab.test:5050 - - docker build -t $IMAGE_TAG . - - docker push $IMAGE_TAG - tags: - - "runner-for-#{project.name}" - YAML - }]) + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: + <<~YAML + build: + image: "#{docker_client_version}" + stage: build + services: + - name: "#{docker_client_version}-dind" + command: ["--insecure-registry=gitlab.test:5050"] + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + script: + - docker login -u #{auth_user} -p #{auth_token} gitlab.test:5050 + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + tags: + - "runner-for-#{project.name}" + YAML + } + ] + ) end end @@ -181,39 +188,47 @@ module QA end end - context "when tls is enabled" do - it "pushes image and deletes tag", :registry_tls, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347591' do + context 'when tls is enabled' do + it( + 'pushes image and deletes tag', + :registry_tls, + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347591' + ) do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([{ - file_path: '.gitlab-ci.yml', - content: - <<~YAML - build: - image: docker:19.03.12 - stage: build - services: - - name: docker:19.03.12-dind - command: - - /bin/sh - - -c - - | - apk add --no-cache openssl - true | openssl s_client -showcerts -connect gitlab.test:5050 > /usr/local/share/ca-certificates/gitlab.test.crt - update-ca-certificates - dockerd-entrypoint.sh || exit - variables: - IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - script: - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD gitlab.test:5050 - - docker build -t $IMAGE_TAG . - - docker push $IMAGE_TAG - tags: - - "runner-for-#{project.name}" - YAML - }]) + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: + <<~YAML + build: + image: docker:19.03.12 + stage: build + services: + - name: docker:19.03.12-dind + command: + - /bin/sh + - -c + - | + apk add --no-cache openssl + true | openssl s_client -showcerts -connect gitlab.test:5050 > /usr/local/share/ca-certificates/gitlab.test.crt + update-ca-certificates + dockerd-entrypoint.sh || exit + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD gitlab.test:5050 + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + tags: + - "runner-for-#{project.name}" + YAML + } + ] + ) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb index 4c15b7c7f99..f95bcc59db1 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb @@ -43,7 +43,10 @@ module QA end end - it "pushes and pulls a helm chart", testcase: params[:testcase] do + it "pushes and pulls a helm chart", testcase: params[:testcase], quarantine: { + type: :stale, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391649' + } do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding) 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 cda45efd828..3fb5c921187 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 @@ -41,7 +41,11 @@ module QA 'using a ci job token' => { authentication_token_type: :ci_job_token, maven_header_name: 'Job-Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347579', + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/373189', + type: :stale + } } } end @@ -60,7 +64,7 @@ module QA end end - it 'pushes and pulls a maven package', testcase: params[:testcase] do + it 'pushes and pulls a maven package', testcase: params[:testcase], quarantine: params[:quarantine] do 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) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb index a9d66c93fac..d86ce09c4e1 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb @@ -31,7 +31,10 @@ module QA end end - it 'pushes and pulls a maven package via gradle', testcase: params[:testcase] do + it 'pushes and pulls a maven package via gradle', testcase: params[:testcase], quarantine: { + type: :stale, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391650' + } do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven/gradle', 'gradle_upload_package.yaml.erb')).result(binding) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb index 0e1ac4d861d..c2cbec3fbb7 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb @@ -97,7 +97,10 @@ module QA end end - it 'publishes a nuget package at the project endpoint and installs it from the group endpoint', testcase: params[:testcase] do + it 'publishes a nuget package at the project endpoint and installs it from the group endpoint', testcase: params[:testcase], quarantine: { + type: :stale, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391648' + } do Flow::Login.sign_in Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do @@ -133,14 +136,14 @@ module QA { file_path: 'otherdotnet.csproj', content: <<~EOF - <Project Sdk="Microsoft.NET.Sdk"> + <Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <OutputType>Exe</OutputType> - <TargetFramework>net7.0</TargetFramework> - </PropertyGroup> + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net7.0</TargetFramework> + </PropertyGroup> - </Project> + </Project> EOF } ] diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb index 2b7dd8fb673..b44020ddfce 100644 --- a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb +++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Monitor', product_group: :respond do + RSpec.describe 'Monitor', :smoke, product_group: :respond do describe 'Alert settings' do shared_examples 'sends test alert' do it 'creates new alert' do @@ -24,6 +24,7 @@ module QA before do Flow::Login.sign_in project.visit! + Flow::AlertSettings.go_to_monitor_settings end context( @@ -35,7 +36,8 @@ module QA end before do - Flow::AlertSettings.setup_http_endpoint(payload: payload) + Flow::AlertSettings.setup_http_endpoint_integration + Flow::AlertSettings.send_test_alert(payload: payload) end it_behaves_like 'sends test alert' @@ -73,7 +75,8 @@ module QA end before do - Flow::AlertSettings.setup_prometheus(payload: payload) + Flow::AlertSettings.setup_prometheus_integration + Flow::AlertSettings.send_test_alert(payload: payload) end it_behaves_like 'sends test alert' diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb new file mode 100644 index 00000000000..565f56b90ec --- /dev/null +++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Monitor', product_group: :respond do + describe 'Alert' do + shared_examples 'new alert' do + it 'automatically creates new incident' do + Page::Project::Menu.perform(&:go_to_monitor_incidents) + Page::Project::Monitor::Incidents::Index.perform do |index| + expect(index).to have_incident + end + end + end + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-alerts' + project.description = 'Project for alerts' + end + end + + before do + Flow::Login.sign_in + project.visit! + Flow::AlertSettings.go_to_monitor_settings + Flow::AlertSettings.enable_create_incident + end + + context( + 'when using HTTP endpoint integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/388469' + ) do + before do + Flow::AlertSettings.setup_http_endpoint_integration + Flow::AlertSettings.send_test_alert + end + + it_behaves_like 'new alert' + end + + context( + 'when using Prometheus integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390123' + ) do + before do + Flow::AlertSettings.setup_prometheus_integration + Flow::AlertSettings.send_test_alert(integration_type: 'prometheus') + end + + it_behaves_like 'new alert' + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb index 9d4aff59e48..96db10c1683 100644 --- a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Monitor', product_group: :respond do + RSpec.describe 'Monitor', :smoke, product_group: :respond do describe 'Alert settings' do shared_examples 'sends test alert using authorization key' do |type| it 'creates new alert', :aggregate_failures do @@ -22,7 +22,8 @@ module QA Page::Project::Menu.perform(&:go_to_monitor_alerts) Page::Project::Monitor::Alerts::Index.perform do |index| - expect(index).to have_alert_with_title(alert_title) + expect { index.has_alert_with_title?(alert_title) } + .to eventually_be_truthy.within(max_duration: 60, reload_page: index) end end end @@ -36,9 +37,14 @@ module QA let(:alert_title) { Faker::Lorem.word } + let(:credentials) do + Flow::AlertSettings.integration_credentials + end + before do Flow::Login.sign_in project.visit! + Flow::AlertSettings.go_to_monitor_settings end context( @@ -49,8 +55,8 @@ module QA { title: alert_title, description: alert_title } end - let(:credentials) do - Flow::AlertSettings.setup_http_endpoint(send: false) + before do + Flow::AlertSettings.setup_http_endpoint_integration end it_behaves_like 'sends test alert using authorization key', 'http' @@ -87,8 +93,8 @@ module QA } end - let(:credentials) do - Flow::AlertSettings.setup_prometheus(send: false) + before do + Flow::AlertSettings.setup_prometheus_integration end it_behaves_like 'sends test alert using authorization key' diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb new file mode 100644 index 00000000000..70874e46f27 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Monitor', :orchestrated, :smtp, :requires_admin, product_group: :respond do + describe 'Alert' do + shared_examples 'notification on new alert', :aggregate_failures do + it 'sends email to user' do + expect { email_subjects }.to eventually_include(alert_email_subject).within(max_duration: 60) + expect(recipient_email_addresses).to include(user.email) + end + end + + let!(:admin_api_client) { Runtime::API::Client.as_admin } + + let!(:user) do + Resource::User.fabricate_via_api! do |user| + user.api_client = admin_api_client + user.hard_delete_on_api_removal = true + end + end + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-alerts' + project.description = 'Project for alerts' + end + end + + let(:alert_title) { Faker::Lorem.word } + let(:mail_hog_api) { Vendor::MailHog::API.new } + let(:alert_email_subject) { "#{project.name} | Alert: #{alert_title}" } + let(:http_payload) { { title: alert_title, description: alert_title } } + + let(:prometheus_payload) do + { + version: '4', + groupKey: nil, + status: 'firing', + receiver: '', + groupLabels: {}, + commonLabels: {}, + commonAnnotations: {}, + externalURL: '', + alerts: [ + { + startsAt: Time.now, + generatorURL: Faker::Internet.url, + endsAt: nil, + status: 'firing', + labels: { gitlab_environment_name: Faker::Lorem.word }, + annotations: + { + title: alert_title, + gitlab_y_label: 'status' + } + } + ] + } + end + + before do + Flow::Login.sign_in + project.visit! + Flow::AlertSettings.go_to_monitor_settings + Flow::AlertSettings.enable_email_notification + end + + context 'when user is a maintainer' do + before do + project.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + end + + context( + 'when using HTTP endpoint integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389993' + ) do + before do + send_http_alert + end + + it_behaves_like 'notification on new alert' + end + + context( + 'when using Prometheus integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389994' + ) do + before do + send_prometheus_alert + end + + it_behaves_like 'notification on new alert' + end + end + + context 'when user is an owner' do + before do + project.add_member(user, Resource::Members::AccessLevel::OWNER) + end + + context( + 'when using HTTP endpoint integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390145' + ) do + before do + send_http_alert + end + + it_behaves_like 'notification on new alert' + end + + context( + 'when using Prometheus integration', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390144' + ) do + before do + send_prometheus_alert + end + + it_behaves_like 'notification on new alert' + end + end + + private + + def send_http_alert + Flow::AlertSettings.setup_http_endpoint_integration + Flow::AlertSettings.send_test_alert(payload: http_payload) + end + + def send_prometheus_alert + Flow::AlertSettings.setup_prometheus_integration + Flow::AlertSettings.send_test_alert(payload: prometheus_payload) + end + + def mail_hog_messages + mail_hog_api.fetch_messages + end + + def email_subjects + mail_hog_messages.map(&:subject) + end + + def recipient_email_addresses + mail_hog_messages.map(&:to) + end + end + end +end diff --git a/qa/qa/specs/features/shared_contexts/advanced_search_shared_context.rb b/qa/qa/specs/features/shared_contexts/advanced_search_shared_context.rb index c6836d11803..805e5ce782a 100644 --- a/qa/qa/specs/features/shared_contexts/advanced_search_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/advanced_search_shared_context.rb @@ -8,18 +8,25 @@ module QA QA::EE::Resource::Settings::Elasticsearch.fabricate_via_api! unless advanced_search_on end - after do - Runtime::Search.disable_elasticsearch(api_client) if !advanced_search_on && !api_client.nil? - end + # TODO: convert check_advanced_search_status method to use the API instead of the UI once the functionality exists + # https://gitlab.com/gitlab-org/gitlab/-/issues/382849 and then we can resume turning off advanced search after the + # tests as in the `after` block here. For now the advanced search tests will have the side effect of turning on + # advanced search if it wasn't enabled before the tests run. + + # after do + # Runtime::Search.disable_elasticsearch(api_client) if !advanced_search_on && !api_client.nil? + # end - # TODO: convert this method to use the API instead of the UI once the functionality exists - # https://gitlab.com/gitlab-org/gitlab/-/issues/382849 def check_advanced_search_status Flow::Login.sign_in - QA::Page::Main::Menu.perform do |menu| - menu.search_for('lorem ipsum') + QA::Support::Retrier.retry_on_exception( + max_attempts: Runtime::Search::RETRY_MAX_ITERATION, + sleep_interval: Runtime::Search::RETRY_SLEEP_INTERVAL) do + QA::Page::Main::Menu.perform do |menu| + menu.search_for('lorem ipsum') + end + page.has_text?('Advanced search is enabled') end - page.has_text?('Advanced search is enabled') 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 index 853f427db12..4bd81ccdf36 100644 --- 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 @@ -32,7 +32,7 @@ module QA 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") + group.avatar = File.new(File.join(Runtime::Path.fixtures_path, 'designs', 'tanuki.jpg'), "r") end end diff --git a/qa/qa/specs/knapsack_runner.rb b/qa/qa/specs/knapsack_runner.rb index 4908553e43d..0c4f938ee28 100644 --- a/qa/qa/specs/knapsack_runner.rb +++ b/qa/qa/specs/knapsack_runner.rb @@ -4,6 +4,8 @@ module QA module Specs class KnapsackRunner def self.run(args) + QA::Support::KnapsackReport.configure! + allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator Knapsack.logger.info '==== Knapsack specs to execute =====' diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb index 0e6e3973de9..1bf189ed6ac 100644 --- a/qa/qa/specs/spec_helper.rb +++ b/qa/qa/specs/spec_helper.rb @@ -11,6 +11,7 @@ QA::Support::GitlabAddress.define_gitlab_address_attribute! QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run QA::Runtime::AllureReport.configure! QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) +QA::Support::KnapsackReport.configure! # Enable zero monkey patching mode before loading any other RSpec code. RSpec.configure(&:disable_monkey_patching!) diff --git a/qa/qa/support/matchers/eventually_matcher.rb b/qa/qa/support/matchers/eventually_matcher.rb index 3f451f89246..3fdc5b711b0 100644 --- a/qa/qa/support/matchers/eventually_matcher.rb +++ b/qa/qa/support/matchers/eventually_matcher.rb @@ -22,6 +22,7 @@ module QA be include match + have_content be_truthy be_falsey be_empty @@ -140,9 +141,9 @@ module QA # @param [Boolean] negate # @return [String] def fail_message(negate: false) - "#{e}:\n\nexpected #{negate ? 'not ' : ''}to #{description}\n\n"\ - "last attempt was: #{@result.nil? ? 'nil' : actual_formatted}\n\n"\ - "Diff:#{diff}" + "#{e}:\n\nexpected #{negate ? 'not ' : ''}to #{description}\n\n" \ + "last attempt was: #{@result.nil? ? 'nil' : actual_formatted}\n\n" \ + "Diff:#{diff}" end # Formatted expect diff --git a/qa/qa/tools/ci/test_results.rb b/qa/qa/tools/ci/test_results.rb deleted file mode 100644 index 635b69f6ca0..00000000000 --- a/qa/qa/tools/ci/test_results.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true - -module QA - module Tools - module Ci - class TestResults - include Helpers - - def initialize(pipeline_name, test_report_job_name, report_path) - @pipeline_name = pipeline_name - @test_report_job_name = test_report_job_name - @report_path = report_path - end - - # Get test report artifacts from downstream pipeline - # - # @param [String] pipeline_name - # @param [String] test_report_job_name - # @param [String] report_path - # @return [void] - def self.get(pipeline_name, test_report_job_name, report_path) - new(pipeline_name, test_report_job_name, report_path).download_test_results - end - - # Download test results from child pipeline - # - # @return [void] - def download_test_results - logger.info("Fetching test results for '#{pipeline_name}'") - - logger.debug(" fetching pipeline id of '#{pipeline_name}' child pipeline") - downstream_pipeline_id = api_get("#{pipelines_url(pipeline_id)}/bridges") - .find { |bridge| bridge[:name] == pipeline_name } - &.dig(:downstream_pipeline, :id) - return logger.error("Child pipeline '#{pipeline_name}' not found!") unless downstream_pipeline_id - - logger.debug(" fetching job id of test report job") - job_id = api_get("#{pipelines_url(downstream_pipeline_id)}/jobs") - .find { |job| job[:name] == test_report_job_name } - &.fetch(:id) - return logger.error("Test report job '#{test_report_job_name}' not found!") unless job_id - - logger.debug(" fetching test results artifact archive") - response = api_get("/projects/#{project_id}/jobs/#{job_id}/artifacts", raw_response: true) - - logger.info("Extracting test result archive") - system("unzip", "-o", "-d", report_path, response.file.path) - end - - private - - attr_reader :pipeline_name, :test_report_job_name, :report_path - - # Base get pipeline url - # - # @param [Integer] id - # @return [String] - def pipelines_url(id) - "/projects/#{project_id}/pipelines/#{id}" - end - - # Current pipeline id - # - # @return [String] - def pipeline_id - ENV["CI_PIPELINE_ID"] - end - - # Current project id - # - # @return [String] - def project_id - ENV["CI_PROJECT_ID"] - end - end - end - end -end diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb index 0cec6b2a1ea..337c6772a06 100644 --- a/qa/spec/resource/api_fabricator_spec.rb +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -90,12 +90,12 @@ RSpec.describe QA::Resource::ApiFabricator do context 'when creating a resource' do before do - allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(raw_post) end it 'returns the resource URL' 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) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(raw_post) expect(subject.fabricate_via_api!).to eq(resource_web_url) end @@ -112,7 +112,7 @@ 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) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(raw_post) expect { subject.fabricate_via_api! }.to raise_error do |error| expect(error.class).to eql(described_class::ResourceFabricationFailedError) @@ -128,7 +128,7 @@ RSpec.describe QA::Resource::ApiFabricator do allow(QA::Support::Loglinking).to receive(: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) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(response) expect { subject.fabricate_via_api! }.to raise_error do |error| expect(error.class).to eql(described_class::ResourceFabricationFailedError) @@ -149,7 +149,7 @@ RSpec.describe QA::Resource::ApiFabricator do 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) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(response) expect { subject.fabricate_via_api! }.to raise_error do |error| expect(error.class).to eql(described_class::ResourceFabricationFailedError) @@ -195,7 +195,7 @@ RSpec.describe QA::Resource::ApiFabricator do let(:transformed_resource) { { existing: 'foo', new: 'foobar', web_url: resource_web_url } } it 'transforms the resource' do - expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body, {}).and_return(raw_post) expect(subject).to receive(:transform_api_resource).with(response).and_return(transformed_resource) subject.fabricate_via_api! diff --git a/qa/spec/runtime/script_extensions/interceptor_spec.rb b/qa/spec/runtime/script_extensions/interceptor_spec.rb index 28e8007973c..d7835b28071 100644 --- a/qa/spec/runtime/script_extensions/interceptor_spec.rb +++ b/qa/spec/runtime/script_extensions/interceptor_spec.rb @@ -3,7 +3,7 @@ RSpec.describe 'Interceptor' do let(:browser) { Capybara.current_session } # need a real host for the js runtime - let(:url) { "file://#{__dir__}/../../../qa/fixtures/script_extensions/test.html" } + let(:url) { "file://#{File.join(Runtime::Path.fixtures_path, 'script_extensions', 'test.html')}" } before(:context) do skip 'Only can test for chrome' unless QA::Runtime::Env.can_intercept? diff --git a/qa/tasks/ci.rake b/qa/tasks/ci.rake index 84a26e3e555..aaf691de1b5 100644 --- a/qa/tasks/ci.rake +++ b/qa/tasks/ci.rake @@ -54,15 +54,15 @@ namespace :ci do append_to_file(env_file, "QA_FEATURE_FLAGS='#{feature_flags}'") end - desc "Download test results from downstream pipeline" - task :download_test_results, [:trigger_name, :test_report_job_name, :report_path] do |_, args| - QA::Tools::Ci::TestResults.get(args[:trigger_name], args[:test_report_job_name], args[:report_path]) - end - desc "Export test run metrics to influxdb" task :export_test_metrics, [:glob] do |_, args| raise("Metrics file glob pattern is required") unless args[:glob] QA::Tools::Ci::TestMetrics.export(args[:glob]) end + + desc "Get available QA environment variables" + task :env_var_name_list do + puts Gitlab::QA::Runtime::Env.variables.keys.join("\n") + end end diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake index c502d1cbb4a..5e60703ced3 100644 --- a/qa/tasks/knapsack.rake +++ b/qa/tasks/knapsack.rake @@ -20,6 +20,7 @@ namespace :knapsack do test_stage_name = args[:stage_name] knapsack_reports = ENV["QA_KNAPSACK_REPORTS"]&.split(",") ci_token = ENV["QA_GITLAB_CI_TOKEN"] + QA::Support::KnapsackReport.configure! reports = if knapsack_reports knapsack_reports @@ -43,6 +44,7 @@ namespace :knapsack do desc "Merge and upload knapsack report" task :upload, [:glob] do |_task, args| + QA::Support::KnapsackReport.configure! QA::Support::KnapsackReport.upload_report(args[:glob]) end |