diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
commit | 3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch) | |
tree | 3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /qa | |
parent | 9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff) |
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'qa')
164 files changed, 2409 insertions, 1502 deletions
diff --git a/qa/.confiner/master.yml b/qa/.confiner/master.yml index e6fc3e68747..f58ea5de017 100644 --- a/qa/.confiner/master.yml +++ b/qa/.confiner/master.yml @@ -13,22 +13,3 @@ ref: master actions: - quarantine - -- name: Dequarantine E2E tests in Master that pass consistently - plugin: - name: gitlab # https://gitlab.com/gitlab-org/quality/confiner/-/blob/main/doc/plugins/gitlab.md - args: - threshold: 10 # at least 10 passes consecutively with no failures to be a candidate for dequarantine - private_token: $QA_GITLAB_CI_TOKEN - - # we do not run quarantined jobs automatically on master, but we still commit to master - project_id: gitlab-org/quality/nightly # https://gitlab.com/gitlab-org/quality/nightly/ - target_project: gitlab-org/gitlab # https://gitlab.com/gitlab-org/gitlab - failure_issue_labels: QA,Quality - failure_issue_prefix: "Failure in " - pwd: qa # E2E specs reside in the qa subdirectory - timeout: 30 - ref: master - job_pattern: '.+-quarantine' - actions: - - dequarantine diff --git a/qa/.confiner/nightly.yml b/qa/.confiner/nightly.yml deleted file mode 100644 index 78089525b0e..00000000000 --- a/qa/.confiner/nightly.yml +++ /dev/null @@ -1,19 +0,0 @@ -- name: Quarantine E2E tests in Nightly that fail consistently - plugin: - name: gitlab - args: - threshold: 3 - private_token: $QA_GITLAB_CI_TOKEN - project_id: gitlab-org/quality/nightly # https://gitlab.com/gitlab-org/quality/nightly/ - target_project: gitlab-org/gitlab - failure_issue_labels: QA,Quality,found:nightly - failure_issue_prefix: "Failure in " - pwd: qa - timeout: 30 - ref: master - environment: - name: nightly - pattern: 'pipeline: :nightly' - job_pattern: '^((?!quarantine).)*$' - actions: - - quarantine diff --git a/qa/Dockerfile b/qa/Dockerfile index 2bf668abc49..bd9cd166701 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -9,8 +9,6 @@ LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" ENV DEBIAN_FRONTEND="noninteractive" # Override config path to make sure local config doesn't override it when building image locally ENV BUNDLE_APP_CONFIG=/home/gitlab/.bundle -# Use webdriver preinstalled in the base image -ENV WD_INSTALL_DIR=/usr/local/bin ## # Install system libs diff --git a/qa/Gemfile b/qa/Gemfile index cc814a43c1c..a06914ca7de 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,11 +2,11 @@ source 'https://rubygems.org' -gem 'gitlab-qa', '~> 10', '>= 10.4.1', require: 'gitlab/qa' -gem 'gitlab_quality-test_tooling', '~> 0.4.0', require: false +gem 'gitlab-qa', '~> 11', '>= 11.2.0', require: 'gitlab/qa' +gem 'gitlab_quality-test_tooling', '~> 0.8.1', require: false 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.39.1' +gem 'capybara', '~> 3.39.2' gem 'capybara-screenshot', '~> 1.0.26' gem 'rake', '~> 13', '>= 13.0.6' gem 'rspec', '~> 3.12' @@ -24,9 +24,8 @@ gem 'parallel', '~> 1.23' gem 'rainbow', '~> 3.1.1' gem 'rspec-parameterized', '~> 1.0.0' gem 'octokit', '~> 6.1.1' -gem "faraday-retry", "~> 2.1" -gem 'webdrivers', '~> 5.2' -gem 'zeitwerk', '~> 2.6', '>= 2.6.7' +gem "faraday-retry", "~> 2.2" +gem 'zeitwerk', '~> 2.6', '>= 2.6.8' gem 'influxdb-client', '~> 2.9' gem 'terminal-table', '~> 3.0.2', require: false gem 'slack-notifier', '~> 2.4', require: false @@ -40,7 +39,7 @@ gem 'chemlab', '~> 0.10' gem 'chemlab-library-www-gitlab-com', '~> 0.1', '>= 0.1.1' # dependencies for jenkins client -gem 'nokogiri', '~> 1.14', '>= 1.14.4' +gem 'nokogiri', '~> 1.15', '>= 1.15.2' gem 'deprecation_toolkit', '~> 2.0.3', require: false diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index ae9b31386b4..e4e89e7e2e1 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -29,7 +29,7 @@ GEM debug_inspector (>= 0.0.1) builder (3.2.4) byebug (11.1.3) - capybara (3.39.1) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) @@ -69,7 +69,7 @@ GEM faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.0) - faraday-retry (2.1.0) + faraday-retry (2.2.0) faraday (~> 2.0) ffi (1.15.5) ffi-compiler (1.0.1) @@ -99,21 +99,21 @@ GEM fog-core nokogiri (>= 1.5.11, < 2.0.0) formatador (0.3.0) - gitlab (4.18.0) - httparty (~> 0.18) + gitlab (4.19.0) + httparty (~> 0.20) terminal-table (>= 1.5.1) - gitlab-qa (10.4.1) + gitlab-qa (11.2.0) activesupport (~> 6.1) - gitlab (~> 4.18.0) + gitlab (~> 4.19) http (~> 5.0) nokogiri (~> 1.10) parallel (>= 1, < 2) rainbow (>= 3, < 4) table_print (= 1.5.7) zeitwerk (>= 2, < 3) - gitlab_quality-test_tooling (0.4.0) - activesupport (~> 6.1) - gitlab (~> 4.18.0) + gitlab_quality-test_tooling (0.8.1) + activesupport (>= 6.1, < 7.1) + gitlab (~> 4.19) http (~> 5.0) nokogiri (~> 1.10) parallel (>= 1, < 2) @@ -141,8 +141,8 @@ GEM google-apis-core (>= 0.7.2, < 2.a) google-apis-sqladmin_v1beta4 (0.36.0) google-apis-core (>= 0.7.2, < 2.a) - google-apis-storage_v1 (0.18.0) - google-apis-core (>= 0.7, < 2.a) + google-apis-storage_v1 (0.19.0) + google-apis-core (>= 0.9.0, < 2.a) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) googleauth (1.2.0) @@ -185,13 +185,13 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2023.0218.1) mini_mime (1.1.0) - mini_portile2 (2.8.1) + mini_portile2 (2.8.2) minitest (5.18.0) multi_json (1.15.0) multi_xml (0.6.0) netrc (0.11.0) - nokogiri (1.14.4) - mini_portile2 (~> 2.8.0) + nokogiri (1.15.2) + mini_portile2 (~> 2.8.2) racc (~> 1.4) octokit (6.1.1) faraday (>= 1, < 3) @@ -201,7 +201,7 @@ GEM parallel (1.23.0) parallel_tests (4.2.1) parallel - parser (3.1.3.0) + parser (3.2.2.1) ast (~> 2.4.1) proc_to_ast (0.1.0) coderay @@ -300,15 +300,11 @@ GEM watir (7.1.0) regexp_parser (>= 1.2, < 3) selenium-webdriver (~> 4.0) - webdrivers (5.2.0) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0) webrick (1.7.0) websocket (1.2.9) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.7) + zeitwerk (2.6.8) PLATFORMS ruby @@ -317,21 +313,21 @@ DEPENDENCIES activesupport (~> 6.1.7.2) airborne (~> 0.3.7) allure-rspec (~> 2.20.0) - capybara (~> 3.39.1) + capybara (~> 3.39.2) capybara-screenshot (~> 1.0.26) chemlab (~> 0.10) chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1) confiner (~> 0.4) deprecation_toolkit (~> 2.0.3) faker (~> 3.2) - faraday-retry (~> 2.1) + faraday-retry (~> 2.2) fog-core (= 2.1.0) fog-google (~> 1.19) - gitlab-qa (~> 10, >= 10.4.1) - gitlab_quality-test_tooling (~> 0.4.0) + gitlab-qa (~> 11, >= 11.2.0) + gitlab_quality-test_tooling (~> 0.8.1) influxdb-client (~> 2.9) knapsack (~> 4.0) - nokogiri (~> 1.14, >= 1.14.4) + nokogiri (~> 1.15, >= 1.15.2) octokit (~> 6.1.1) parallel (~> 1.23) parallel_tests (~> 4.2, >= 4.2.1) @@ -349,8 +345,7 @@ DEPENDENCIES slack-notifier (~> 2.4) terminal-table (~> 3.0.2) warning (~> 1.3) - webdrivers (~> 5.2) - zeitwerk (~> 2.6, >= 2.6.7) + zeitwerk (~> 2.6, >= 2.6.8) BUNDLED WITH - 2.4.13 + 2.4.14 diff --git a/qa/gdk/Dockerfile b/qa/gdk/Dockerfile deleted file mode 100644 index ed8f3f317eb..00000000000 --- a/qa/gdk/Dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -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/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk new file mode 100644 index 00000000000..cf9cea69056 --- /dev/null +++ b/qa/gdk/Dockerfile.gdk @@ -0,0 +1,145 @@ +# Multi-stage Dockerfile for packaging gdk as executable docker image +# Each stage can be executed in parallel and cached separately based on changes for respective component +# Caches are cleaned for each stage to reduce the footprint of exported cache layers + +ARG BASE_TAG=master + +FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-slim-ruby-3.1-golang-1.19-rust-1.65-node-16.14-postgresql-13:rubygems-3.4-git-2.36-lfs-2.9-yarn-1.22 as gdk-base + +RUN set -eux; \ + groupadd gdk -g 1000; \ + useradd gdk -m -s /bin/bash -u 1000 -g 1000 + +ENV GEM_HOME=/home/gdk/.gem \ + GEM_PATH=/home/gdk/.gem \ + PATH=$PATH:/home/gdk/.gem/bin + +WORKDIR /home/gdk + +# Reinstall libpcre2 and install postgresql +# See: https://gitlab.com/gitlab-org/gitaly/-/issues/4085 +RUN set -eux; \ + rm -f /usr/lib/libpcre2*; \ + apt-get update && apt-get install -y --reinstall --no-install-recommends \ + libpcre2-16-0 \ + libpcre2-32-0 \ + libpcre2-8-0 \ + libpcre2-dev \ + libpcre2-posix2 \ + && apt-get install -y --no-install-recommends postgresql-13; \ + apt-get autoclean -y + +# Clone GDK and install system dependencies, purge system git +ARG GDK_SHA +ENV GDK_SHA=${GDK_SHA:-main} +RUN set -eux; \ + git -c advice.detachedHead=false clone --depth 1 https://gitlab.com/gitlab-org/gitlab-development-kit.git; \ + git -C gitlab-development-kit fetch --depth 1 origin ${GDK_SHA}; \ + git -C gitlab-development-kit -c advice.detachedHead=false checkout ${GDK_SHA}; \ + mkdir -p gitlab-development-kit/gitlab && chown -R gdk:gdk gitlab-development-kit; \ + apt-get update && apt-get install -y --no-install-recommends $(grep -o '^[^#]*' gitlab-development-kit/packages_debian.txt); \ + apt-get remove -y git git-lfs; \ + apt-get autoclean -y && apt-get autoremove -y + +# Allow passwordless /etc/hosts update by gdk user +RUN echo "gdk ALL=(ALL) NOPASSWD: /usr/bin/tee -a /etc/hosts" >> /etc/sudoers + +USER gdk +WORKDIR /home/gdk/gitlab-development-kit + +# Install GDK and gem dependencies +ARG GDK_VERSION +ENV GDK_VERSION=${GDK_VERSION:-0.2.16} +RUN set -eux; \ + gem install gitlab-development-kit -v ${GDK_VERSION} && touch .gitlab-gdk-gem; \ + bundle install; \ + rm -rf ${GEM_HOME}/cache + +COPY --chown=gdk:gdk qa/gdk/gdk.yml ./ + +# Build gitaly +# +COPY --chown=gdk:gdk GITALY_SERVER_VERSION ./gitlab/ +RUN set -eux; \ + make gitaly-setup; \ + rm -rf gitaly/_build/cache \ + gitaly/_build/deps/git/source \ + gitaly/_build/deps/libgit2/source \ + gitaly/_build/deps \ + gitaly/_build/intermediate \ + ${GEM_HOME}/cache \ + && go clean -cache + +# Build gitlab-shell +# +COPY --chown=gdk:gdk GITLAB_SHELL_VERSION ./gitlab/ +RUN set -eux; \ + make gitlab-shell-setup; \ + rm -rf ${GEM_HOME}/cache \ + && go clean -cache + +# Build gitlab-workhorse +# +COPY --chown=gdk:gdk VERSION GITLAB_WORKHORSE_VERSION ./gitlab/ +COPY --chown=gdk:gdk workhorse ./gitlab/workhorse +RUN set -eux; \ + make gitlab-workhorse-setup \ + && mv gitlab/workhorse ./; \ + rm -rf ${GEM_HOME}/cache \ + && go clean -cache + +# Build metrics-exporter +# +COPY --chown=gdk:gdk GITLAB_METRICS_EXPORTER_VERSION ./gitlab/ +RUN set -eux; \ + make gitlab-metrics-exporter-setup; \ + go clean -cache + +# Install gitlab gem dependencies +# +COPY --chown=gdk:gdk Gemfile Gemfile.lock ./gitlab/ +COPY --chown=gdk:gdk vendor/gems ./gitlab/vendor/gems +RUN make .gitlab-bundle && rm -rf ${GEM_HOME}/cache + +# Install gitlab npm dependencies +# +COPY --chown=gdk:gdk package.json yarn.lock ./gitlab/ +COPY --chown=gdk:gdk scripts/frontend/postinstall.js ./gitlab/scripts/frontend/postinstall.js +RUN make .gitlab-yarn && yarn cache clean + +# Executable gdk image +# +FROM registry.gitlab.com/gitlab-org/gitlab/gitlab-qa-gdk-base:${BASE_TAG} as gdk + +ENV GITLAB_LICENSE_MODE=test \ + GDK_KILL_CONFIRM=true + +# Copy code +COPY --chown=gdk:gdk ./ ./gitlab/ +COPY --chown=gdk:gdk qa/gdk/entrypoint ../ + +# Create missing pids folder and sync compiled workhorse +RUN mkdir -p gitlab/tmp/pids \ + && rsync -a --remove-source-files workhorse/ gitlab/workhorse/ + +# Set up GDK +RUN make \ + redis/redis.conf \ + all \ + && gdk kill \ + && rm -rf ${GEM_HOME}/cache \ + gitaly/_build/cache \ + gitaly/_build/deps/git/source \ + gitaly/_build/deps/libgit2/source \ + gitaly/_build/deps \ + gitaly/_build/intermediate \ + && go clean -modcache \ + && go clean -cache + +ENTRYPOINT [ "/home/gdk/entrypoint" ] +CMD [ "gdk", "tail" ] + +HEALTHCHECK --interval=10s --timeout=1s --start-period=5s --retries=17 \ + CMD curl --fail http://0.0.0.0:3000/users/sign_in || exit 1 + +EXPOSE 3000 diff --git a/qa/gdk/Dockerfile.gdk.dockerignore b/qa/gdk/Dockerfile.gdk.dockerignore new file mode 100644 index 00000000000..ef1074ed833 --- /dev/null +++ b/qa/gdk/Dockerfile.gdk.dockerignore @@ -0,0 +1,21 @@ +.bundle/ +danger/ +doc/ +log/*.log +node_modules/ +rubocop/ +tmp/* + +db/fixtures/ +!db/fixtures/development/01_admin.rb +!db/fixtures/development/02_application_settings.rb +!db/fixtures/development/25_api_personal_access_token.rb + +ee/db/fixtures/ +!ee/db/fixtures/development/10_license_file.rb + +scripts/ +!scripts/frontend + +qa/ +!qa/gdk diff --git a/qa/gdk/entrypoint b/qa/gdk/entrypoint new file mode 100755 index 00000000000..3b86f19b9b2 --- /dev/null +++ b/qa/gdk/entrypoint @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +# host file can't be modified during docker build because it is created on container start +# this makes for a more portable solution as other alternative is to use `--add-host` option for 'docker run' command +# We want a non-loopback ip otherwise some services (maybe just workhorse?) bind to localhost and can't be accessed +# from outside the container +echo "$(hostname -i) gdk.test" | sudo tee -a /etc/hosts > /dev/null + +# make sure we don't override existing BUNDLE_PATH which happens on CI where all job variables are also passed in to service +unset BUNDLE_PATH + +gdk start + +# /builds folder is present when running as a service on CI +if [ -d /builds ]; then + exec "$@" | tee -a /builds/gdk.log +else + exec "$@" | tee -a ${HOME}/gitlab-development-kit/gitlab/log/gdk.log +fi diff --git a/qa/gdk/gdk.yml b/qa/gdk/gdk.yml index 0494cd0d3c1..649ac9a60c6 100644 --- a/qa/gdk/gdk.yml +++ b/qa/gdk/gdk.yml @@ -6,12 +6,15 @@ webpack: live_reload: false sourcemaps: false incremental: false + static: true gdk: ask_to_restart_after_update: false auto_reconfigure: false overwrite_changes: true quiet: false gitlab: + cache_classes: true + lefthook_enabled: false rails: bootsnap: false hostname: gdk.test diff --git a/qa/gdk/launch b/qa/gdk/launch deleted file mode 100755 index 249484b417c..00000000000 --- a/qa/gdk/launch +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -COMMIT_REF=${1:-$CI_COMMIT_SHA} -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 bin/qa Test::Instance::All http://gdk.test:3000 -- $RSPEC_ARGS diff --git a/qa/lib/gitlab/page/group/settings/billing.rb b/qa/lib/gitlab/page/group/settings/billing.rb index 1cb31c995f6..0d25a012db3 100644 --- a/qa/lib/gitlab/page/group/settings/billing.rb +++ b/qa/lib/gitlab/page/group/settings/billing.rb @@ -33,8 +33,13 @@ module Gitlab # Waits for subscription to be synced and UI to be updated # # @param subscription_plan [String] - def wait_for_subscription(subscription_plan, page:) - ::QA::Support::Waiter.wait_until(max_duration: 30, sleep_interval: 3, reload_page: page) do + def wait_for_subscription(subscription_plan) + ::QA::Support::Waiter.wait_until( + max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT, + sleep_interval: 2, + reload_page: Chemlab.configuration.browser.session, + message: "Subscription plan '#{subscription_plan}' failed to appear" + ) do billing_plan_header.match?(/currently using the #{subscription_plan} saas plan/i) end end diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb index a6d9cfa7846..11d1dd78dbe 100644 --- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb +++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb @@ -33,9 +33,8 @@ module Gitlab div :project div :storage_type_legend span :container_registry_size - div :purchased_usage_total + div :storage_purchased, 'data-testid': 'storage-purchased' div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/ - div :additional_storage_alert, text: /purchase additional storage/ # Pending members div :pending_members @@ -50,13 +49,10 @@ module Gitlab additional_ci_minutes[/(\d+){2}/] end - # Waits and Checks if storage available alert presents on the page - # - # @return [Boolean] True if the alert presents, false if not after 5 second wait - def additional_storage_available? - additional_storage_alert_element.wait_until(timeout: 5, &:present?) - rescue Watir::Wait::TimeoutError - false + def additional_ci_minutes_added? + # When opening the Usage quotas page, Seats quota tab is opened briefly even when url is to a different tab + ::QA::Support::WaitForRequests.wait_for_requests + additional_ci_minutes? end # Waits and Checks if storage project data loaded @@ -72,15 +68,37 @@ module Gitlab # # @return [Float] Total purchased storage value in GiB def total_purchased_storage - additional_storage_alert_element.wait_until(&:present?) + ::QA::Support::WaitForRequests.wait_for_requests - purchased_usage_total[/(\d+){2}.\d+/].to_f + storage_purchased[/(\d+){2}.\d+/].to_f end - def additional_ci_minutes_added? - # When opening the Usage quotas page, Seats quota tab is opened briefly even when url is to a different tab - ::QA::Support::WaitForRequests.wait_for_requests - additional_ci_minutes? + # Waits for additional CI minutes to be available on the page + def wait_for_additional_ci_minutes_available + ::QA::Support::Waiter.wait_until( + max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT, + sleep_interval: 2, + reload_page: Chemlab.configuration.browser.session, + message: 'Expected additional CI minutes but they did not appear.' + ) do + additional_ci_minutes_added? + end + end + + # Waits for additional CI minutes amount to match the expected number of minutes + # + # @param [String] minutes + def wait_for_additional_ci_minute_limits(minutes) + wait_for_additional_ci_minutes_available + + ::QA::Support::Waiter.wait_until( + max_duration: ::QA::Support::Helpers::Zuora::ZUORA_TIMEOUT, + sleep_interval: 2, + reload_page: Chemlab.configuration.browser.session, + message: "Expected additional CI minutes to equal #{minutes}" + ) do + additional_ci_limits == minutes + end end end end diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.stub.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.stub.rb index 6c38625bb3b..8099d1cf53a 100644 --- a/qa/lib/gitlab/page/group/settings/usage_quotas.stub.rb +++ b/qa/lib/gitlab/page/group/settings/usage_quotas.stub.rb @@ -533,51 +533,27 @@ module Gitlab # This is a stub, used for indexing. The method is dynamically generated. end - # @note Defined as +div :purchased_usage_total_free+ - # @return [String] The text content or value of +purchased_usage_total_free+ - def purchased_usage_total_free + # @note Defined as +div :storage_purchased+ + # @return [String] The text content or value of +storage_purchased+ + def storage_purchased # This is a stub, used for indexing. The method is dynamically generated. end # @example # Gitlab::Page::Group::Settings::UsageQuotas.perform do |usage_quotas| - # expect(usage_quotas.purchased_usage_total_free_element).to exist + # expect(usage_quotas.storage_purchased_element).to exist # end # @return [Watir::Div] The raw +Div+ element - def purchased_usage_total_free_element + def storage_purchased_element # This is a stub, used for indexing. The method is dynamically generated. end # @example # Gitlab::Page::Group::Settings::UsageQuotas.perform do |usage_quotas| - # expect(usage_quotas).to be_purchased_usage_total_free + # expect(usage_quotas).to be_storage_purchased # end - # @return [Boolean] true if the +purchased_usage_total_free+ element is present on the page - def purchased_usage_total_free? - # This is a stub, used for indexing. The method is dynamically generated. - end - - # @note Defined as +span :purchased_usage_total+ - # @return [String] The text content or value of +purchased_usage_total+ - def purchased_usage_total - # This is a stub, used for indexing. The method is dynamically generated. - end - - # @example - # Gitlab::Page::Group::Settings::UsageQuotas.perform do |usage_quotas| - # expect(usage_quotas.purchased_usage_total_element).to exist - # end - # @return [Watir::Span] The raw +Span+ element - def purchased_usage_total_element - # This is a stub, used for indexing. The method is dynamically generated. - end - - # @example - # Gitlab::Page::Group::Settings::UsageQuotas.perform do |usage_quotas| - # expect(usage_quotas).to be_purchased_usage_total - # end - # @return [Boolean] true if the +purchased_usage_total+ element is present on the page - def purchased_usage_total? + # @return [Boolean] true if the +storage_purchased+ element is present on the page + def storage_purchased? # This is a stub, used for indexing. The method is dynamically generated. end diff --git a/qa/lib/gitlab/page/trials/select.rb b/qa/lib/gitlab/page/trials/select.rb index 6eaf6003837..d4cf54805ea 100644 --- a/qa/lib/gitlab/page/trials/select.rb +++ b/qa/lib/gitlab/page/trials/select.rb @@ -4,7 +4,7 @@ module Gitlab module Page module Trials class Select < Chemlab::Page - path '/-/trials/select' + path '/-/trials/new?step=trial' button :select_group, 'data-testid': 'base-dropdown-toggle' div :group_dropdown, 'data-testid': 'base-dropdown-menu' diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 2587da17739..24398e8eace 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -7,17 +7,24 @@ module QA def perform_before_hooks if QA::Runtime::Env.admin_personal_access_token.present? - QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username, - QA::Runtime::Env.admin_personal_access_token) + QA::Resource::PersonalAccessTokenCache.set_token_for_username( + QA::Runtime::User.admin_username, + QA::Runtime::Env.admin_personal_access_token + ) end if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present? - QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username, - QA::Runtime::Env.personal_access_token) + QA::Resource::PersonalAccessTokenCache.set_token_for_username( + QA::Runtime::Env.user_username, + QA::Runtime::Env.personal_access_token + ) end QA::Runtime::Logger.info("Browser: #{QA::Runtime::Env.browser}") - QA::Runtime::Logger.info("Browser version: #{QA::Runtime::Env.browser_version}") + + if QA::Runtime::Env.use_selenoid? + QA::Runtime::Logger.info("Browser version: #{QA::Runtime::Env.selenoid_browser_version}") + end # The login page could take some time to load the first time it is visited. # We visit the login page and wait for it to properly load only once before the tests. diff --git a/qa/qa/fixtures/mocks/import/github.yml b/qa/qa/fixtures/mocks/import/github.yml index 6ee6be471c4..16d038ed091 100644 --- a/qa/qa/fixtures/mocks/import/github.yml +++ b/qa/qa/fixtures/mocks/import/github.yml @@ -575,8 +575,6 @@ path: /repos/gitlab-qa-github/import-test/pulls method: GET query_params: - per_page: '100' - sort: created state: all headers: Host: api.github.com diff --git a/qa/qa/fixtures/package_managers/npm/npm_install_package_group.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_install_package_group.yaml.erb new file mode 100644 index 00000000000..a5ec4f298e2 --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/npm_install_package_group.yaml.erb @@ -0,0 +1,21 @@ +image: node:latest + +stages: + - install + +install: + stage: install + script: + - "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/groups/<%= another_project.group.id %>/-/packages/npm/" + - "npm install <%= package.name %>" + cache: + key: ${CI_COMMIT_REF_NAME} + paths: + - node_modules/ + artifacts: + paths: + - node_modules/ + only: + - "<%= another_project.default_branch %>" + tags: + - "runner-for-<%= another_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_package_group.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_package_group.yaml.erb new file mode 100644 index 00000000000..13c00cd17c4 --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/npm_upload_package_group.yaml.erb @@ -0,0 +1,14 @@ +image: node:latest + +stages: + - deploy + +deploy: + stage: deploy + script: + - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc + - npm publish + only: + - "<%= project.default_branch %>" + tags: + - "runner-for-<%= project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/package_instance.json.erb b/qa/qa/fixtures/package_managers/npm/package.json.erb index 46fecf97e2c..46fecf97e2c 100644 --- a/qa/qa/fixtures/package_managers/npm/package_instance.json.erb +++ b/qa/qa/fixtures/package_managers/npm/package.json.erb diff --git a/qa/qa/fixtures/package_managers/npm/package_project.json.erb b/qa/qa/fixtures/package_managers/npm/package_project.json.erb deleted file mode 100644 index 46fecf97e2c..00000000000 --- a/qa/qa/fixtures/package_managers/npm/package_project.json.erb +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "<%= package.name %>", - "version": "1.0.0", - "description": "Example package for GitLab npm registry", - "publishConfig": { - "@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/" - } -}
\ No newline at end of file diff --git a/qa/qa/flow/purchase.rb b/qa/qa/flow/purchase.rb index 32c4f469207..2eee15b874c 100644 --- a/qa/qa/flow/purchase.rb +++ b/qa/qa/flow/purchase.rb @@ -3,7 +3,7 @@ module QA module Flow module Purchase - include QA::Support::Helpers::Plan + include Support::Helpers::Plan extend self diff --git a/qa/qa/mobile/page/project/issue/show.rb b/qa/qa/mobile/page/project/issue/show.rb index df96a318806..4184c9871cc 100644 --- a/qa/qa/mobile/page/project/issue/show.rb +++ b/qa/qa/mobile/page/project/issue/show.rb @@ -13,7 +13,7 @@ module QA base.class_eval do view 'app/assets/javascripts/issues/show/components/header_actions.vue' do - element :issue_actions_dropdown + element :mobile_dropdown element :mobile_close_issue_button element :mobile_reopen_issue_button end @@ -21,12 +21,12 @@ module QA end def click_close_issue_button - find('[data-qa-selector="issue_actions_dropdown"] > button').click + find('[data-testid="mobile-dropdown"] > button').click find_element(:mobile_close_issue_button, visible: false).click end def has_reopen_issue_button? - find('[data-qa-selector="issue_actions_dropdown"] > button').click + find('[data-testid="mobile-dropdown"] > button').click has_element?(:mobile_reopen_issue_button) end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 7452077de67..6c01fa9f344 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -5,6 +5,14 @@ require 'capybara/dsl' module QA module Page class Base + # Generic matcher for common css selectors like: + # - class name '.someclass' + # - id '#someid' + # - selection by attributes 'input[attribute=name][value=value]' + # + # @return [Regex] + CSS_SELECTOR_PATTERN = /^(\.[a-z-]+|\#[a-z-]+)+|([a-z]+\[.*\])$/i + prepend Support::Page::Logging prepend Mobile::Page::Base if QA::Runtime::Env.mobile_layout? @@ -386,6 +394,7 @@ module QA def element_selector_css(name, *attributes) return name.selector_css if name.is_a? Page::Element + return name if name.is_a?(String) && name.match?(CSS_SELECTOR_PATTERN) Page::Element.new(name, *attributes).selector_css end @@ -506,8 +515,8 @@ module QA return element_when_flag_disabled if has_element?(element_when_flag_disabled, visible: visibility) raise ElementNotFound, - "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \ - "The relevant feature flag is #{feature_flag}" + "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \ + "The relevant feature flag is #{feature_flag}" end end end diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb index 5ee5d4978e5..a57ef38f768 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_deprecated.vue' do + base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue' do element :blob_viewer_file_content end end diff --git a/qa/qa/page/component/issuable/common.rb b/qa/qa/page/component/issuable/common.rb index 768a15a4551..2f116822a12 100644 --- a/qa/qa/page/component/issuable/common.rb +++ b/qa/qa/page/component/issuable/common.rb @@ -10,8 +10,8 @@ module QA def self.included(base) super - base.view 'app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue' do - element :title_content, required: true + base.view 'app/assets/javascripts/issues/show/components/title.vue' do + element :issue_title, required: true end end end diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb index 0a31dee2b4f..06a3c6a8845 100644 --- a/qa/qa/page/component/issuable/sidebar.rb +++ b/qa/qa/page/component/issuable/sidebar.rb @@ -49,7 +49,7 @@ module QA end base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do - element :edit_link + element :edit_button end base.view 'app/helpers/dropdowns_helper.rb' do @@ -59,7 +59,7 @@ module QA def assign_milestone(milestone) wait_milestone_block_finish_loading do - click_element(:edit_link) + click_element(:edit_button) click_on(milestone.title) end @@ -134,7 +134,7 @@ module QA def select_labels(labels) within_element(:labels_block) do - click_element(:edit_link) + click_element(:edit_button) labels.each do |label| within_element(:labels_dropdown_content) do @@ -144,7 +144,7 @@ module QA end end - click_element(:title_content) # to blur dropdown + click_element(:issue_title) # to blur dropdown end def toggle_more_assignees_link diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb index db9492ea516..84cc481945f 100644 --- a/qa/qa/page/component/note.rb +++ b/qa/qa/page/component/note.rb @@ -168,7 +168,7 @@ module QA def select_filter_with_text(text) retry_on_exception do - click_element(:title_content) + click_element(:issue_title) click_element :discussion_preferences_dropdown find_element(:filter_menu_item, text: text).click diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb index 05d59acd8e8..12695ddd5b0 100644 --- a/qa/qa/page/component/snippet.rb +++ b/qa/qa/page/component/snippet.rb @@ -43,7 +43,7 @@ module QA element :snippet_embed_dropdown end - base.view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do + base.view 'app/assets/javascripts/vue_shared/components/clone_dropdown/clone_dropdown.vue' do element :copy_http_url_button element :copy_ssh_url_button end @@ -70,6 +70,10 @@ module QA element :note_author_content end + base.view 'app/views/shared/notes/_notes_with_form.html.haml' do + element :notes_list + end + base.view 'app/views/projects/notes/_more_actions_dropdown.html.haml' do element :more_actions_dropdown element :delete_comment_button @@ -216,6 +220,10 @@ module QA end end + def within_notes_list(&block) + within_element :notes_list, &block + end + def has_syntax_highlighting?(language) within_element(:blob_viewer_file_content) do find('.line')['lang'].to_s == language diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 10529ed69e1..4a81d34b32e 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -17,6 +17,14 @@ module QA element :new_project_button end + view 'app/views/dashboard/projects/_blank_state_welcome.html.haml' do + element :new_project_button + end + + view 'app/views/dashboard/projects/_blank_state_admin_welcome.html.haml' do + element :new_project_button + end + def has_project_with_access_role?(project_name, access_role) within_element(:project_content, text: project_name) do has_element?(:user_role_content, text: access_role) diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 6bfdf98587b..f0e67627dca 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -4,6 +4,8 @@ require 'active_support/core_ext/array/extract_options' module QA module Page + # Gitlab element css selector builder using data-testid attribute + # class Element attr_reader :name, :attributes @@ -13,9 +15,7 @@ module QA @attributes[:pattern] ||= selector options.each do |option| - if option.is_a?(String) || option.is_a?(Regexp) - @attributes[:pattern] = option - end + @attributes[:pattern] = option if option.is_a?(String) || option.is_a?(Regexp) end end @@ -28,7 +28,7 @@ module QA end def selector_css - %Q([data-qa-selector="#{@name}"]#{additional_selectors},.#{selector}) + %(#{qa_selector}#{additional_selectors},.#{selector}) end def expression @@ -40,14 +40,26 @@ module QA end def matches?(line) - !!(line =~ /["']#{name}['"]|#{expression}/) + !!(line =~ /["']#{name}['"]|["']#{convert_to_kebabcase(name)}['"]|#{expression}/) end private + def convert_to_kebabcase(text) + text.to_s.tr('_', '-') + end + + def qa_selector + [ + %([data-testid="#{name}"]#{additional_selectors}), + %([data-testid="#{convert_to_kebabcase(name)}"]#{additional_selectors}), + %([data-qa-selector="#{name}"]#{additional_selectors}) + ].join(',') + end + def additional_selectors @attributes.dup.delete_if { |attr| attr == :pattern || attr == :required }.map do |key, value| - %Q([data-qa-#{key.to_s.tr('_', '-')}="#{value}"]) + %([data-qa-#{key.to_s.tr('_', '-')}="#{value}"]) end.join end end diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb index e66019279ce..665fef0d794 100644 --- a/qa/qa/page/file/edit.rb +++ b/qa/qa/page/file/edit.rb @@ -8,24 +8,14 @@ module QA include Shared::CommitButton include Shared::Editor - view 'app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js' do - element :editor_toolbar_button, "qaSelector: 'editor_toolbar_button'" # rubocop:disable QA/ElementWithPattern - end - def has_markdown_preview?(component, content) within_element(:source_editor_preview_container) do has_css?(component, exact_text: content) end end - def wait_for_markdown_preview(component, content) - return if has_markdown_preview?(component, content) - - raise ElementNotFound, %("Couldn't find #{component} element with content '#{content}') - end - - def click_editor_toolbar - click_element(:editor_toolbar_button) + def preview + click_link('Preview') end end end diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb index 011b1ea5d81..173baa21160 100644 --- a/qa/qa/page/file/show.rb +++ b/qa/qa/page/file/show.rb @@ -12,21 +12,15 @@ module QA element :lock_button end - view 'app/assets/javascripts/vue_shared/components/web_ide_link.vue' do - element :edit_button - end - view 'app/assets/javascripts/vue_shared/components/actions_button.vue' do element :action_dropdown element :edit_menu_item, ':data-qa-selector="`${action.key}_menu_item`"' # rubocop:disable QA/ElementWithPattern + element :webide_menu_item, ':data-qa-selector="`${action.key}_menu_item`"' # rubocop:disable QA/ElementWithPattern end def click_edit - within_element(:action_dropdown) do - click_button(class: 'dropdown-toggle-split') - click_element(:edit_menu_item) - click_element(:edit_button) - end + click_element(:action_dropdown) + click_element(:edit_menu_item) end def click_delete diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index 157bc3abaf6..c166023a620 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -13,9 +13,12 @@ module QA prepend SubMenus::SuperSidebar::Main prepend SubMenus::SuperSidebar::Build prepend SubMenus::SuperSidebar::Operate + prepend SubMenus::SuperSidebar::Deploy end def click_group_members_item + return go_to_members if Runtime::Env.super_sidebar_enabled? + hover_group_information do within_submenu do click_element(:sidebar_menu_item_link, menu_item: 'Members') @@ -83,7 +86,9 @@ module QA end end - def go_to_dependency_proxy + def go_to_group_dependency_proxy + return go_to_dependency_proxy if Runtime::Env.super_sidebar_enabled? + hover_group_packages do within_submenu do click_element(:sidebar_menu_item_link, menu_item: 'Dependency Proxy') diff --git a/qa/qa/page/group/sub_menus/super_sidebar/deploy.rb b/qa/qa/page/group/sub_menus/super_sidebar/deploy.rb new file mode 100644 index 00000000000..4d205898bf6 --- /dev/null +++ b/qa/qa/page/group/sub_menus/super_sidebar/deploy.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Group + module SubMenus + module SuperSidebar + module Deploy + extend QA::Page::PageConcern + + def self.prepended(base) + super + + base.class_eval do + include QA::Page::SubMenus::SuperSidebar::Deploy + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/group/sub_menus/super_sidebar/main.rb b/qa/qa/page/group/sub_menus/super_sidebar/main.rb index e470c03b9e5..1bc6fa84935 100644 --- a/qa/qa/page/group/sub_menus/super_sidebar/main.rb +++ b/qa/qa/page/group/sub_menus/super_sidebar/main.rb @@ -8,7 +8,7 @@ module QA module Main extend QA::Page::PageConcern - def self.included(base) + def self.prepended(base) super base.class_eval do diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index bea01a5bbc7..3a836210dea 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -42,6 +42,7 @@ module QA element :saml_login_button element :github_login_button element :oidc_login_button + element :gitlab_oauth_login_button end view 'app/views/layouts/devise.html.haml' do @@ -108,6 +109,7 @@ module QA # Happens on clean GDK installations when seeded root admin password is expired # def set_up_new_password_if_required(user:, skip_page_validation:) + Support::WaitForRequests.wait_for_requests return unless has_content?('Set up new password', wait: 1) Profile::Password.perform do |new_password_page| @@ -189,11 +191,16 @@ module QA click_element :saml_login_button end - def sign_in_with_oidc + def sign_in_with_gitlab_oidc set_initial_password_if_present click_element :oidc_login_button end + def sign_in_with_gitlab_oauth + set_initial_password_if_present + click_element :gitlab_oauth_login_button + end + def sign_out_and_sign_in_as(user:) Menu.perform(&:sign_out_if_signed_in) has_sign_in_tab? @@ -238,9 +245,7 @@ module QA Support::WaitForRequests.wait_for_requests - wait_until(sleep_interval: 5, message: '502 - GitLab is taking too much time to respond') do - has_no_text?('GitLab is taking too much time to respond') - end + wait_for_gitlab_to_respond # For debugging invalid login attempts has_notice?('Invalid login or password') @@ -249,7 +254,14 @@ module QA terms.accept_terms if terms.visible? end + Flow::UserOnboarding.onboard_user + + wait_for_gitlab_to_respond + Page::Main::Menu.perform(&:enable_new_navigation) if Runtime::Env.super_sidebar_enabled? + + wait_for_gitlab_to_respond + Page::Main::Menu.validate_elements_present! unless skip_page_validation end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 934aa182b12..7e0337035e3 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -17,11 +17,11 @@ module QA # Define alternative navbar (super sidebar) which does not yet implement all the same elements view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do element :navbar, required: true # TODO: rename to sidebar once it's default implementation - element :user_menu, required: !Runtime::Env.phone_layout? - element :user_avatar_content, required: !Runtime::Env.phone_layout? end view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do + element :user_menu, required: !Runtime::Env.phone_layout? + element :user_avatar_content, required: !Runtime::Env.phone_layout? element :sign_out_link element :edit_profile_link end @@ -32,6 +32,7 @@ module QA view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do element :global_search_button + element :stop_impersonation_link end view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do @@ -252,16 +253,11 @@ module QA end def has_admin_area_link?(wait: Capybara.default_max_wait_time) - within_top_menu do - click_element(:navbar_dropdown, title: 'Menu') - has_element?(:admin_area_link, wait: wait) - end - end + return super if Runtime::Env.super_sidebar_enabled? - def has_no_admin_area_link?(wait: Capybara.default_max_wait_time) within_top_menu do click_element(:navbar_dropdown, title: 'Menu') - has_no_element?(:admin_area_link, wait: wait) + has_element?(:admin_area_link, wait: wait) end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index e10db796f8e..e0ec5d50bc5 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -118,7 +118,7 @@ module QA end view 'app/views/projects/merge_requests/_mr_title.html.haml' do - element :edit_button + element :edit_title_button element :title_content, required: true end @@ -201,13 +201,6 @@ module QA def click_diffs_tab # Do not wait for spinner due to https://gitlab.com/gitlab-org/gitlab/-/issues/398584 click_element(:diffs_tab, skip_finished_loading_check: true) - - # If the diff isn't available when we navigate to the Changes tab - # we must reload the page. https://gitlab.com/gitlab-org/gitlab/-/issues/398557 - wait_until(reload: true, skip_finished_loading_check_on_refresh: true) do - QA::Runtime::Logger.debug('Ensuring that diff has loaded async') - has_element?(:file_tree_button, skip_finished_loading_check: true, wait: 5) - end end def click_pipeline_link @@ -218,7 +211,7 @@ module QA # Click by JS is needed to bypass the Moved MR actions popover # Change back to regular click_element when moved_mr_sidebar FF is removed # Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460 - click_by_javascript(find_element(:edit_button)) + click_by_javascript(find_element(:edit_title_button, skip_finished_loading_check: true)) end def fast_forward_not_possible? @@ -474,10 +467,6 @@ module QA click_element(:submit_commit_button) end - def cancel_auto_merge! - click_element(:cancel_auto_merge_button) - end - def mr_widget_text find_element(:mr_widget_content).text end diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb index a97d0afd160..af328f876f7 100644 --- a/qa/qa/page/project/branches/show.rb +++ b/qa/qa/page/project/branches/show.rb @@ -5,7 +5,7 @@ module QA module Project module Branches class Show < Page::Base - view 'app/assets/javascripts/branches/components/delete_branch_button.vue' do + view 'app/assets/javascripts/branches/components/branch_more_actions.vue' do element :delete_branch_button end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 05f38968718..c2f334c930a 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -8,6 +8,7 @@ module QA include Page::Component::Note include Page::Component::DesignManagement include Page::Component::Issuable::Sidebar + include Page::Component::Issuable::Common # We need to check phone_layout? instead of mobile_layout? here # since tablets have the regular top navigation bar prepend Mobile::Page::Project::Issue::Show if Runtime::Env.phone_layout? @@ -17,16 +18,11 @@ module QA end view 'app/assets/javascripts/issues/show/components/header_actions.vue' do - element :close_issue_button - element :reopen_issue_button - element :issue_actions_ellipsis_dropdown + element :toggle_issue_state_button + element :desktop_dropdown element :delete_issue_button end - view 'app/assets/javascripts/issues/show/components/title.vue' do - element :title_content, required: true - end - view 'app/assets/javascripts/related_issues/components/add_issuable_form.vue' do element :add_issue_button end @@ -69,27 +65,29 @@ module QA # Click by JS is needed to bypass the Moved MR actions popover # Change back to regular click_element when moved_mr_sidebar FF is removed # Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460 - click_by_javascript(find_element(:close_issue_button)) + click_by_javascript(find_element(:toggle_issue_state_button, text: 'Close issue')) end def has_reopen_issue_button? - has_element?(:reopen_issue_button) + has_element?(:toggle_issue_state_button, text: 'Reopen issue') end def has_delete_issue_button? # Click by JS is needed to bypass the Moved MR actions popover # Change back to regular click_element when moved_mr_sidebar FF is removed # Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/385460 - click_by_javascript(find('[data-qa-selector="issue_actions_ellipsis_dropdown"] > button')) + click_by_javascript(find('[data-testid="desktop-dropdown"] > button')) has_element?(:delete_issue_button) end def delete_issue has_delete_issue_button? - click_element(:delete_issue_button, - Page::Modal::DeleteIssue, - wait: Support::Repeater::DEFAULT_MAX_WAIT_TIME) + click_element( + :delete_issue_button, + Page::Modal::DeleteIssue, + wait: Support::Repeater::DEFAULT_MAX_WAIT_TIME + ) Page::Modal::DeleteIssue.perform(&:confirm_delete_issue) diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index 23b3ee61077..534a4da8426 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -18,6 +18,7 @@ module QA if Runtime::Env.super_sidebar_enabled? include Page::SubMenus::SuperSidebar::Manage + include Page::SubMenus::SuperSidebar::Deploy include SubMenus::SuperSidebar::Plan include SubMenus::SuperSidebar::Settings include SubMenus::SuperSidebar::Code diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 2455555f06c..41c61aaecc8 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -7,8 +7,15 @@ module QA class Show < QA::Page::Base include Component::CiBadgeLink + # TODO: remove element with FF pipeline_details_header_vue removal view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do - element :pipeline_header, required: true + element :pipeline_header + end + + # TODO: make this a requirement at page render - `required: true` + # together with FF pipeline_details_header_vue removal + view 'app/assets/javascripts/pipelines/components/pipeline_details_header.vue' do + element :pipeline_details_header end view 'app/views/projects/pipelines/_info.html.haml' do @@ -56,7 +63,12 @@ module QA end def running?(wait: 0) - within_element(:pipeline_header) do + # TODO: remove condition check together with FF pipeline_details_header_vue removal + # issue: https://gitlab.com/gitlab-org/gitlab/-/issues/411442 + + element = has_element?(:pipeline_details_header) ? :pipeline_details_header : :pipeline_header + + within_element(element) do page.has_content?('running', wait: wait) end end diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb index 6a95d461639..c70b6e76cfe 100644 --- a/qa/qa/page/project/pipeline_editor/show.rb +++ b/qa/qa/page/project/pipeline_editor/show.rb @@ -11,8 +11,6 @@ module QA view 'app/assets/javascripts/ci/pipeline_editor/components/file_nav/branch_switcher.vue' do element :branch_selector_button, required: true - element :branch_menu_item_button - element :branch_menu_container end view 'app/assets/javascripts/ci/pipeline_editor/components/commit/commit_form.vue' do @@ -70,17 +68,6 @@ module QA click_element(:pipeline_editor_app) end - def open_branch_selector_dropdown - click_element(:branch_selector_button) - end - - def select_branch_from_dropdown(branch_name) - wait_for_animated_element(:branch_menu_container) - click_element(:branch_menu_item_button, text: branch_name) - - wait_for_requests - end - def source_branch_name find_element(:source_branch_field).value end diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb index 493ec08d023..70eff31bfa9 100644 --- a/qa/qa/page/project/secure/configuration_form.rb +++ b/qa/qa/page/project/secure/configuration_form.rb @@ -9,15 +9,13 @@ module QA view 'app/assets/javascripts/security_configuration/components/app.vue' do element :security_configuration_container - element :security_configuration_history_link + element :security_view_history_link end view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do - element :dependency_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern - element :sast_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern + element :feature_status element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern element :dependency_scanning_mr_button, "`${feature.type}_mr_button`" # rubocop:disable QA/ElementWithPattern - element :license_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern end view 'app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue' do @@ -25,15 +23,15 @@ module QA end def has_security_configuration_history_link? - has_element?(:security_configuration_history_link) + has_element?(:security_view_history_link) end def has_no_security_configuration_history_link? - has_no_element?(:security_configuration_history_link) + has_no_element?(:security_view_history_link) end def click_security_configuration_history_link - click_element(:security_configuration_history_link) + click_element(:security_view_history_link) end def click_sast_enable_button @@ -44,40 +42,20 @@ module QA click_element(:dependency_scanning_mr_button) end - def has_sast_status?(status_text) - within_element(:sast_status) do - has_text?(status_text) - end - end - - def has_no_sast_status?(status_text) - within_element(:sast_status) do - has_no_text?(status_text) - end - end - - def has_dependency_scanning_status?(status_text) - within_element(:dependency_scanning_status) do - has_text?(status_text) - end + def has_true_sast_status? + has_element?(:feature_status, feature: 'sast_true_status') end - def has_no_dependency_scanning_status?(status_text) - within_element(:dependency_scanning_status) do - has_no_text?(status_text) - end + def has_false_sast_status? + has_element?(:feature_status, feature: 'sast_false_status') end - def has_license_compliance_status?(status_text) - within_element(:license_scanning_status) do - has_text?(status_text) - end + def has_true_dependency_scanning_status? + has_element?(:feature_status, feature: 'dependency_scanning_true_status') end - def has_no_license_compliance_status?(status_text) - within_element(:license_scanning_status) do - has_no_text?(status_text) - end + def has_false_dependency_scanning_status? + has_element?(:feature_status, feature: 'dependency_scanning_false_status') end def has_auto_devops_container? diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb index 1315ed8ca73..4b587e28382 100644 --- a/qa/qa/page/project/settings/ci_variables.rb +++ b/qa/qa/page/project/settings/ci_variables.rb @@ -19,7 +19,8 @@ module QA click_ci_variable_save_button wait_until(reload: false) do - within_element(:ci_variable_table_content) { has_element?(:edit_ci_variable_button) } + # Using data-testid="ci-variable-table" + within_element(:ci_variable_table) { has_element?(:edit_ci_variable_button) } end end @@ -28,7 +29,8 @@ module QA end def click_edit_ci_variable - within_element(:ci_variable_table_content) do + # Using data-testid="ci-variable-table" + within_element(:ci_variable_table) do click_element :edit_ci_variable_button end end diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 3eddd0fd33a..e6b13ed77a0 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -11,9 +11,9 @@ module QA end view 'app/views/protected_branches/_create_protected_branch.html.haml' do - element :allowed_to_push_dropdown + element :select_allowed_to_push_dropdown element :allowed_to_push_dropdown_content - element :allowed_to_merge_dropdown + element :select_allowed_to_merge_dropdown element :allowed_to_merge_dropdown_content end @@ -45,7 +45,7 @@ module QA private def select_allowed(action, allowed) - click_element :"allowed_to_#{action}_dropdown" + click_element :"select_allowed_to_#{action}_dropdown" allowed[:roles] = Resource::ProtectedBranch::Roles::NO_ONE unless allowed.key?(:roles) diff --git a/qa/qa/page/project/settings/services/slack.rb b/qa/qa/page/project/settings/services/slack.rb new file mode 100644 index 00000000000..aaf01bbd6e8 --- /dev/null +++ b/qa/qa/page/project/settings/services/slack.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + module Services + class Slack < Chemlab::Page + strong :slack_text, visible_text: /install/i + + def start_slack_install + slack_link.click + end + + def slack_link + slack_text_element.parent + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index daaee280b84..95a6c840684 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -62,10 +62,6 @@ module QA element :new_file_menu_item end - view 'app/assets/javascripts/vue_shared/components/web_ide_link.vue' do - element :web_ide_button - end - view 'app/views/projects/blob/viewers/_loading.html.haml' do element :spinner_placeholder end @@ -154,7 +150,8 @@ module QA end def open_web_ide! - click_element(:web_ide_button) + click_element(:action_dropdown) + click_element(:webide_menu_item) page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) end @@ -164,7 +161,8 @@ module QA end def has_edit_fork_button? - has_element?(:web_ide_button, text: 'Edit fork in Web IDE') + click_element(:action_dropdown) + has_element?(:webide_menu_item, text: 'Edit fork in Web IDE') end def project_name diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb index b8ebaa10a49..274dd3a58c8 100644 --- a/qa/qa/page/project/sub_menus/repository.rb +++ b/qa/qa/page/project/sub_menus/repository.rb @@ -38,6 +38,8 @@ module QA end def go_to_repository_contributors + return go_to_contributor_statistics if Runtime::Env.super_sidebar_enabled? + hover_repository do within_submenu do click_element(:sidebar_menu_item_link, menu_item: 'Contributor statistics') diff --git a/qa/qa/page/project/sub_menus/super_sidebar/code.rb b/qa/qa/page/project/sub_menus/super_sidebar/code.rb index 44d46725b47..fae7210e3c8 100644 --- a/qa/qa/page/project/sub_menus/super_sidebar/code.rb +++ b/qa/qa/page/project/sub_menus/super_sidebar/code.rb @@ -36,6 +36,10 @@ module QA open_code_submenu('Compare revisions') end + def go_to_merge_requests + open_code_submenu('Merge requests') + end + private def open_code_submenu(sub_menu) diff --git a/qa/qa/page/project/sub_menus/super_sidebar/deploy.rb b/qa/qa/page/project/sub_menus/super_sidebar/deploy.rb new file mode 100644 index 00000000000..f19e18b11d7 --- /dev/null +++ b/qa/qa/page/project/sub_menus/super_sidebar/deploy.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module SubMenus + module SuperSidebar + module Deploy + extend QA::Page::PageConcern + + def self.included(base) + super + + base.class_eval do + include QA::Page::SubMenus::SuperSidebar::Deploy + end + end + + def go_to_releases + open_deploy_submenu("Releases") + end + + def go_to_feature_flags + open_deploy_submenu("Feature flags") + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/sub_menus/super_sidebar/main.rb b/qa/qa/page/project/sub_menus/super_sidebar/main.rb index 63641248b15..bf2619737ab 100644 --- a/qa/qa/page/project/sub_menus/super_sidebar/main.rb +++ b/qa/qa/page/project/sub_menus/super_sidebar/main.rb @@ -8,14 +8,6 @@ module QA module Main extend QA::Page::PageConcern - def self.included(base) - super - - base.class_eval do - include QA::Page::SubMenus::SuperSidebar::Main - end - end - def click_project click_element(:nav_item_link, submenu_item: 'Project overview') end diff --git a/qa/qa/page/project/sub_menus/super_sidebar/plan.rb b/qa/qa/page/project/sub_menus/super_sidebar/plan.rb index fe45bb6bb65..fe77789f371 100644 --- a/qa/qa/page/project/sub_menus/super_sidebar/plan.rb +++ b/qa/qa/page/project/sub_menus/super_sidebar/plan.rb @@ -25,6 +25,10 @@ module QA def go_to_open_jira open_plan_submenu("Open Jira") end + + def go_to_issues + open_plan_submenu("Issues") + end end end end diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb index dda329ab582..8432edff046 100644 --- a/qa/qa/page/project/web_ide/edit.rb +++ b/qa/qa/page/project/web_ide/edit.rb @@ -68,7 +68,7 @@ module QA element :delete_button end - view 'app/assets/javascripts/vue_shared/components/confirm_fork_modal.vue' do + view 'app/assets/javascripts/vue_shared/components/web_ide/confirm_fork_modal.vue' do element :fork_project_button element :confirm_fork_modal end @@ -111,8 +111,12 @@ module QA # Used for stablility, due to feature_caching of vscode_web_ide def wait_until_ide_loads - Support::Waiter.wait_until(sleep_interval: 2, max_duration: 120, reload_page: page, - retry_on_exception: true) do + Support::Waiter.wait_until( + sleep_interval: 2, + max_duration: 120, + reload_page: page, + retry_on_exception: true + ) do has_element?(:commit_mode_tab) end end diff --git a/qa/qa/page/project/web_ide/vscode.rb b/qa/qa/page/project/web_ide/vscode.rb index 1a9fad56a10..6a570978dbe 100644 --- a/qa/qa/page/project/web_ide/vscode.rb +++ b/qa/qa/page/project/web_ide/vscode.rb @@ -1,64 +1,183 @@ # frozen_string_literal: true -# VSCode WebIDE is built off an iFrame application therefore we are uanble to use `qa-selectors` +# VSCode WebIDE is built off an iFrame application therefore we are unable to use `qa-selectors` module QA module Page module Project module WebIDE class VSCode < Page::Base - # Use to Pass Test::Sanity::Selectors temporarily until iframe [data-qa-* selector added view 'app/views/shared/_broadcast_message.html.haml' do element :broadcast_notification_container element :close_button end - # Used for stablility, due to feature_caching of vscode_web_ide - def wait_for_ide_to_load - page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) - wait_for_requests - Support::Waiter.wait_until(max_duration: 60, reload_page: page, retry_on_exception: true) do + def has_file_explorer? + page.has_css?('.explorer-folders-view', visible: true) + end + + def right_click_file_explorer + page.find('.explorer-folders-view', visible: true).right_click + end + + def has_new_folder_menu_item? + page.has_css?('[aria-label="New Folder..."]', visible: true) + end + + def click_new_folder_menu_item + page.find('[aria-label="New Folder..."]').click + end + + def enter_new_folder_text_input(name) + page.find('.explorer-item-edited', visible: true) + send_keys(name, :enter) + end + + def has_upload_menu_item? + page.has_css?('[aria-label="Upload..."]', visible: true) + end + + def click_upload_menu_item + page.find('[aria-label="Upload..."]').click + end + + def enter_file_input(file) + page.find('input[type="file"]', visible: false).send_keys(file) + end + + def has_commit_pending_tab? + page.has_css?('.scm-viewlet-label', visible: true) + end + + def click_commit_pending_tab + page.find('.scm-viewlet-label', visible: true).click + end + + def click_commit_tab + page.find('a.codicon-source-control-view-icon', visible: true).click + end + + def has_commit_message_box? + page.has_css?('div.view-lines.monaco-mouse-cursor-text', visible: true) + end + + def enter_commit_message(message) + page.find('div.view-lines.monaco-mouse-cursor-text', visible: true).send_keys(message) + end + + def click_commit_button + page.find('a.monaco-text-button', visible: true).click + end + + def has_notification_box? + page.has_css?('a.monaco-text-button', visible: true) + end + + def create_merge_request + Support::Waiter.wait_until(max_duration: 10, retry_on_exception: true) do within_vscode_editor do - # vscode file_explorer element - page.has_css?('.explorer-folders-view', visible: true) + page.find('.monaco-button[title="Create MR"]').click end end end + def click_new_branch + page.find('.monaco-button[title="Create new branch"]').click + end + + def has_branch_input_field? + page.has_css?('.monaco-findInput', visible: true) + end + + def has_message?(content) + within_vscode_editor do + page.has_content?(content) + end + end + def within_vscode_editor(&block) iframe = find('#ide iframe') page.within_frame(iframe, &block) end + # Used for stablility, due to feature_caching of vscode_web_ide + def wait_for_ide_to_load + page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) + # On test environments we have a broadcast message that can cover the buttons + if has_element?(:broadcast_notification_container, wait: 5) + within_element(:broadcast_notification_container) do + click_element(:close_button) + end + end + + wait_for_requests + Support::Waiter.wait_until(max_duration: 10, reload_page: page, retry_on_exception: true) do + within_vscode_editor do + # Check for webide file_explorer element + has_file_explorer? + end + end + end + def create_new_folder(name) within_vscode_editor do - # Use for stability, WebIDE inside an iframe is finnicky - Support::Waiter.wait_until(max_duration: 60, retry_on_exception: true) do - page.find('.explorer-folders-view').right_click - # new_folder_button - page.has_css?('[aria-label="New Folder..."]', visible: true) - end + right_click_file_explorer + has_new_folder_menu_item? - # Additonal wait for stability, webdriver sometimes moves too fast - Support::Waiter.wait_until(max_duration: 60, retry_on_exception: true) do - page.find('[aria-label="New Folder..."]').click + # Use for stability, WebIDE inside an iframe is finnicky, webdriver sometimes moves too fast + Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do + click_new_folder_menu_item # Verify New Folder button is triggered and textbox is waiting for input - page.find('.explorer-item-edited', visible: true) - send_keys(name, :enter) + enter_new_folder_text_input(name) page.has_content?(name) end end end - def commit_and_push(folder_name) + def commit_and_push(file_name) + commit_toggle(file_name) + push_to_new_branch + end + + def commit_toggle(message) within_vscode_editor do - # Commit Tab - page.find('a.codicon-source-control-view-icon').click - send_keys(folder_name) - page.has_content?(folder_name) - - # Commit Button - page.find('a.monaco-text-button').click - page.has_css?('.notification-list-item-details-row', visible: true) + if has_commit_pending_tab? + click_commit_pending_tab + else + click_commit_tab + end + + has_commit_message_box? + send_keys(message) + page.has_content?(message) + click_commit_button + has_notification_box? + end + end + + def push_to_new_branch + within_vscode_editor do + click_new_branch + has_branch_input_field? + # Typing enter to 'New branch name' popup to take the default branch name + send_keys(:enter) + end + end + + def upload_file(file_path) + wait_for_ide_to_load + within_vscode_editor do + # VSCode eagerly removes the input[type='file'] from click on Upload. + # We need to execute a script on the iframe to stub out the iframes body.removeChild to add it back in. + page.execute_script("document.body.removeChild = function(){};") + + right_click_file_explorer + has_upload_menu_item? + + # Use for stability, WebIDE inside an iframe is finnicky, webdriver sometimes moves too fast + Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do + click_upload_menu_item + enter_file_input(file_path) + end end end end diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb index 769f8accfca..a04fd9092d1 100644 --- a/qa/qa/page/search/results.rb +++ b/qa/qa/page/search/results.rb @@ -4,7 +4,7 @@ module QA module Page module Search class Results < QA::Page::Base - view 'app/assets/javascripts/search/sidebar/components/scope_navigation.vue' do + view 'app/assets/javascripts/search/sidebar/components/scope_legacy_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 diff --git a/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb b/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb index 1fd35e57dc2..e5f2e702e60 100644 --- a/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb +++ b/qa/qa/page/sub_menus/super_sidebar/context_switcher.rb @@ -30,13 +30,22 @@ module QA go_to_context("Admin Area") end + def has_admin_area_link?(wait: Capybara.default_max_wait_time) + open_context_switcher + + has_element?(:nav_item_link, submenu_item: "Admin Area", wait: wait) + end + private def go_to_context(sub_menu) - click_element(:context_switcher) unless has_element?(:context_navigation, wait: 0) - + open_context_switcher click_element(:nav_item_link, submenu_item: sub_menu) end + + def open_context_switcher + click_element(:context_switcher) unless has_element?(:context_navigation, wait: 0) + end end end end diff --git a/qa/qa/page/sub_menus/super_sidebar/deploy.rb b/qa/qa/page/sub_menus/super_sidebar/deploy.rb new file mode 100644 index 00000000000..30e41b82c79 --- /dev/null +++ b/qa/qa/page/sub_menus/super_sidebar/deploy.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Page + module SubMenus + module SuperSidebar + module Deploy + extend QA::Page::PageConcern + + def go_to_package_registry + open_deploy_submenu("Package Registry") + end + + def go_to_container_registry + open_deploy_submenu('Container Registry') + end + + private + + def open_deploy_submenu(sub_menu) + open_submenu("Deploy", sub_menu) + end + end + end + end + end +end diff --git a/qa/qa/page/sub_menus/super_sidebar/manage.rb b/qa/qa/page/sub_menus/super_sidebar/manage.rb index 535b29e607f..369171299b2 100644 --- a/qa/qa/page/sub_menus/super_sidebar/manage.rb +++ b/qa/qa/page/sub_menus/super_sidebar/manage.rb @@ -19,10 +19,6 @@ module QA open_manage_submenu('Labels') end - def go_to_milestones - open_manage_submenu('Milestones') - end - private def open_manage_submenu(sub_menu) diff --git a/qa/qa/page/sub_menus/super_sidebar/operate.rb b/qa/qa/page/sub_menus/super_sidebar/operate.rb index 1ffbb6872da..29f23d74307 100644 --- a/qa/qa/page/sub_menus/super_sidebar/operate.rb +++ b/qa/qa/page/sub_menus/super_sidebar/operate.rb @@ -7,16 +7,8 @@ module QA module Operate extend QA::Page::PageConcern - def go_to_package_registry - open_operate_submenu('Package Registry') - end - - def go_to_container_registry - open_operate_submenu('Container Registry') - end - def go_to_dependency_proxy - open_operate_submenu('Dependency proxy') + open_operate_submenu('Dependency Proxy') end private diff --git a/qa/qa/page/sub_menus/super_sidebar/plan.rb b/qa/qa/page/sub_menus/super_sidebar/plan.rb index e4b9fcf099c..839ba89cedb 100644 --- a/qa/qa/page/sub_menus/super_sidebar/plan.rb +++ b/qa/qa/page/sub_menus/super_sidebar/plan.rb @@ -19,6 +19,10 @@ module QA open_plan_submenu("Wiki") end + def go_to_milestones + open_plan_submenu('Milestones') + end + private def open_plan_submenu(sub_menu) diff --git a/qa/qa/resource/badge_base.rb b/qa/qa/resource/badge_base.rb index 5bb7eb98d4e..bc0adf0857d 100644 --- a/qa/qa/resource/badge_base.rb +++ b/qa/qa/resource/badge_base.rb @@ -3,10 +3,7 @@ module QA module Resource class BadgeBase < Base - attributes :id, - :name, - :link_url, - :image_url + attributes :id, :name, :link_url, :image_url def initialize @name = "qa-badge-#{SecureRandom.hex(8)}" diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 6c03f45bdfd..e12a8f97d95 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -182,11 +182,12 @@ module QA raise NotImplementedError end - def visit!(skip_resp_code_check: false) + def visit!(skip_finished_loading_check: false, skip_resp_code_check: false) Runtime::Logger.info("Visiting #{Rainbow(self.class.name).black.bg(:white)} at #{web_url}") # Just in case an async action is not yet complete - Support::WaitForRequests.wait_for_requests(skip_resp_code_check: skip_resp_code_check) + Support::WaitForRequests.wait_for_requests(skip_finished_loading_check: skip_finished_loading_check, + skip_resp_code_check: skip_resp_code_check) Support::Retrier.retry_until do visit(web_url) @@ -194,15 +195,18 @@ module QA end # Wait until the new page is ready for us to interact with it - Support::WaitForRequests.wait_for_requests(skip_resp_code_check: skip_resp_code_check) + Support::WaitForRequests.wait_for_requests(skip_finished_loading_check: skip_finished_loading_check, + skip_resp_code_check: skip_resp_code_check) end def populate(*attribute_names) attribute_names.each { |attribute_name| public_send(attribute_name) } end - def wait_until(max_duration: 60, sleep_interval: 0.1, &block) - QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval, &block) + def wait_until(max_duration: 60, sleep_interval: 0.1, message: nil, &block) + QA::Support::Waiter.wait_until( + max_duration: max_duration, sleep_interval: sleep_interval, message: message, &block + ) end # Object comparison diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb index 19ad5f1faf2..5196be39a12 100644 --- a/qa/qa/resource/bulk_import_group.rb +++ b/qa/qa/resource/bulk_import_group.rb @@ -3,9 +3,7 @@ module QA module Resource class BulkImportGroup < Group - attributes :source_group, - :destination_group, - :import_id + attributes :source_group, :destination_group, :import_id attribute :import_access_token do api_client.personal_access_token diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb index 253b63e4260..7a180bb189d 100644 --- a/qa/qa/resource/file.rb +++ b/qa/qa/resource/file.rb @@ -4,11 +4,11 @@ module QA module Resource class File < Base attr_accessor :author_email, - :author_name, - :content, - :commit_message, - :name, - :start_branch + :author_name, + :content, + :commit_message, + :name, + :start_branch attr_writer :branch attribute :project do diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index 5e2a567119d..263c2ca2aeb 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -12,12 +12,12 @@ module QA attr_accessor :path, :avatar attributes :id, - :runners_token, - :name, - :full_path, - # Add visibility to enable create private group - :visibility, - :shared_with_groups + :runners_token, + :name, + :full_path, + # Add visibility to enable create private group + :visibility, + :shared_with_groups # Get group projects # @@ -84,6 +84,14 @@ module QA end end + # Get group runners + # + # @param [Hash] **kwargs optional query arguments, see: https://docs.gitlab.com/ee/api/runners.html#list-groups-runners + # @return [Array] + def runners(**kwargs) + auto_paginated_response(request_url(api_runners_path, **kwargs)) + end + # API get path # # @return [String] @@ -112,6 +120,14 @@ module QA "/groups/#{id}" end + # API path to GET runners + # See https://docs.gitlab.com/ee/api/runners.html#list-groups-runners + # + # @return [String] + def api_runners_path + "#{api_get_path}/runners" + end + # Object comparison # Override to make sure we are comparing descendands of GroupBase # diff --git a/qa/qa/resource/group_milestone.rb b/qa/qa/resource/group_milestone.rb index c208270658e..b6dd9ee2dbf 100644 --- a/qa/qa/resource/group_milestone.rb +++ b/qa/qa/resource/group_milestone.rb @@ -4,13 +4,13 @@ module QA module Resource class GroupMilestone < Base attributes :id, - :iid, - :title, - :description, - :start_date, - :due_date, - :updated_at, - :created_at + :iid, + :title, + :description, + :start_date, + :due_date, + :updated_at, + :created_at attribute :group do Group.fabricate_via_api! do |resource| diff --git a/qa/qa/resource/group_runner.rb b/qa/qa/resource/group_runner.rb index d7fa26a1d53..fc08910c73e 100644 --- a/qa/qa/resource/group_runner.rb +++ b/qa/qa/resource/group_runner.rb @@ -21,7 +21,7 @@ module QA def runner(**kwargs) fail_msg = "Wait for runner '#{name}' to register in group '#{group.name}'" Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1, message: fail_msg) do - auto_paginated_response(request_url("/runners", **kwargs)).find { |runner| runner[:description] == name } + group.runners(**kwargs).find { |runner| runner[:description] == name } end end end diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index fb957ccf285..a5d5795b0de 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -14,11 +14,11 @@ module QA end attributes :id, - :iid, - :assignee_ids, - :labels, - :title, - :description + :iid, + :assignee_ids, + :labels, + :title, + :description def initialize @assignee_ids = [] diff --git a/qa/qa/resource/job.rb b/qa/qa/resource/job.rb index 5b0dac9b2df..dc425cc174c 100644 --- a/qa/qa/resource/job.rb +++ b/qa/qa/resource/job.rb @@ -5,8 +5,7 @@ module QA class Job < Base attr_accessor :id, :name, :project - attributes :id, - :project + attributes :id, :project def fabricate_via_api! resource_web_url(api_get) diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 50ef9538fb0..0d1a0bb2cd4 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -24,7 +24,7 @@ module QA :title, :description, :merge_when_pipeline_succeeds, - :merge_status, + :detailed_merge_status, :state, :reviewers @@ -151,13 +151,11 @@ module QA end def merge_via_api! - Support::Waiter.wait_until(sleep_interval: 1) do - QA::Runtime::Logger.debug("Waiting until merge request with id '#{iid}' can be merged") + QA::Runtime::Logger.info("Merging via PUT #{api_merge_path}") - reload!.merge_status == 'can_be_merged' - end + wait_until_mergable - Support::Retrier.retry_on_exception do + Support::Retrier.retry_on_exception(max_attempts: 10, sleep_interval: 5) do response = put(Runtime::API::Request.new(api_client, api_merge_path).url) unless response.code == HTTP_STATUS_OK @@ -184,6 +182,10 @@ module QA end def fabricate_large_merge_request + # requires admin access + QA::Support::Helpers::ImportSource.enable(%w[gitlab_project]) + Flow::Login.sign_in + @project = Resource::ImportProject.fabricate_via_browser_ui! # Setting the name here, since otherwise some tests will look for an existing file in # the project without ever knowing what is in it. @@ -205,7 +207,7 @@ module QA :project_id, :source_project_id, :target_project_id, - :merge_status, + :detailed_merge_status, # we consider mr to still be the same even if users changed :author, :reviewers, @@ -250,6 +252,18 @@ module QA def create_target? !(project.initialize_with_readme && target_branch == project.default_branch) && target_new_branch end + + # Wait until the merge request can be merged. Raises WaitExceededError if the MR can't be merged within 60 seconds + # + # @return [void] + def wait_until_mergable + return if Support::Waiter.wait_until(sleep_interval: 1, raise_on_failure: false, log: false) do + reload!.detailed_merge_status == 'mergeable' + end + + raise Support::Repeater::WaitExceededError, + "Timed out waiting for merge of MR with id '#{iid}'. Final status was '#{detailed_merge_status}'" + end end end end diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb index e57784ca3b5..41c9d5b30f6 100644 --- a/qa/qa/resource/pipeline.rb +++ b/qa/qa/resource/pipeline.rb @@ -35,9 +35,7 @@ module QA def fabricate_via_api! resource_web_url(api_get) - rescue ResourceNotFoundError - super - rescue NoValueError + rescue ResourceNotFoundError, NoValueError super end @@ -49,7 +47,7 @@ module QA "/projects/#{project.id}/pipelines/#{id}" end - def api_pipeline_jobs_path + def api_jobs_path "#{api_get_path}/jobs" end @@ -98,8 +96,8 @@ module QA result[:downstream_pipeline][:id] end - def pipeline_jobs - parse_body(api_get_from(api_pipeline_jobs_path)) + def jobs + parse_body(api_get_from(api_jobs_path)) end end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 37ff2315329..9032777dcaa 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -128,7 +128,12 @@ module QA # If a project is being imported, wait until it completes before we let the test continue. # Otherwise we see Git repository errors # See https://gitlab.com/gitlab-org/gitlab/-/issues/356101 - Support::Retrier.retry_until(max_duration: 60, sleep_interval: 5, retry_on_exception: true) do + Support::Retrier.retry_until( + max_duration: 60, + sleep_interval: 5, + retry_on_exception: true, + message: "Wait for project to be imported" + ) do reload!.api_resource[:import_status] == "finished" end @@ -399,16 +404,29 @@ module QA parse_body(response) end - def pipelines(auto_paginate: false, attempts: 0) + def pipelines(auto_paginate: false, attempts: 0, **kwargs) return parse_body(api_get_from(api_pipelines_path)) unless auto_paginate - auto_paginated_response(request_url(api_pipelines_path, per_page: '100'), attempts: attempts) + auto_paginated_response(request_url(api_pipelines_path, per_page: '100', **kwargs), attempts: attempts) end def latest_pipeline parse_body(api_get_from(api_latest_pipeline_path)) end + # Waits for a pipeline to be available with the attributes as specified. + # + # @param [Hash] **kwargs optional query arguments, see: https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines + # @return [Hash] the pipeline + def wait_for_pipeline(**kwargs) + wait_until(sleep_interval: 1, message: "Wait for pipeline with '#{kwargs}' to be available") do + result = pipelines(auto_paginate: true, **kwargs) + next unless result.present? && result.size == 1 + + result.first + end + end + def jobs response = get(request_url(api_jobs_path)) parse_body(response) diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb index 879c3a4282c..729171c7cfd 100644 --- a/qa/qa/resource/protected_branch.rb +++ b/qa/qa/resource/protected_branch.rb @@ -4,10 +4,10 @@ module QA module Resource class ProtectedBranch < Base attr_accessor :branch_name, - :allowed_to_push, - :allowed_to_merge, - :new_branch, - :require_code_owner_approval + :allowed_to_push, + :allowed_to_merge, + :new_branch, + :require_code_owner_approval attribute :project do Project.fabricate_via_api! do |resource| diff --git a/qa/qa/resource/registry_repository.rb b/qa/qa/resource/registry_repository.rb index 148af353a25..66ccc92bf4c 100644 --- a/qa/qa/resource/registry_repository.rb +++ b/qa/qa/resource/registry_repository.rb @@ -3,8 +3,7 @@ module QA module Resource class RegistryRepository < Base - attr_accessor :name, - :tag_name + attr_accessor :name, :tag_name attribute :project do Project.fabricate_via_api! do |resource| diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb index 1fe35f7a77d..f84ffa6aac3 100644 --- a/qa/qa/resource/repository/commit.rb +++ b/qa/qa/resource/repository/commit.rb @@ -5,12 +5,12 @@ module QA module Repository class Commit < Base attr_accessor :author_email, - :author_name, - :branch, - :commit_message, - :file_path, - :sha, - :start_branch + :author_name, + :branch, + :commit_message, + :file_path, + :sha, + :start_branch attribute :short_id diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index 00bed7ed546..93b820e91c8 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -5,8 +5,8 @@ module QA module Repository class Push < Base attr_accessor :file_name, :file_content, :commit_message, - :branch_name, :new_branch, :output, :repository_http_uri, - :repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name + :branch_name, :new_branch, :output, :repository_http_uri, + :repository_ssh_uri, :ssh_key, :user, :use_lfs, :tag_name attr_writer :remote_branch, :gpg_key_id, :merge_request_push_options diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 16d0f6ad380..abee46c41ab 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -8,18 +8,18 @@ module QA attr_reader :unique_id attr_writer :username, :password attr_accessor :admin, - :provider, - :extern_uid, - :expect_fabrication_success, - :hard_delete_on_api_removal, - :access_level, - :email_domain + :provider, + :extern_uid, + :expect_fabrication_success, + :hard_delete_on_api_removal, + :access_level, + :email_domain attributes :id, - :name, - :first_name, - :last_name, - :email + :name, + :first_name, + :last_name, + :email def initialize @admin = false @@ -177,9 +177,10 @@ module QA def self.all(per_page: 100) response = nil Resource::User.init do |user| - response = user.get(Runtime::API::Request.new(Runtime::API::Client.as_admin, - '/users', - per_page: per_page.to_s).url) + response = user.get(Runtime::API::Request.new( + Runtime::API::Client.as_admin, '/users', per_page: per_page.to_s + ).url) + raise ResourceQueryError unless response.code == 200 end.parse_body(response) end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index b67db792419..7bc4530287c 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -4,8 +4,6 @@ require 'rspec/core' require 'rspec/expectations' require 'capybara/rspec' require 'capybara-screenshot/rspec' -require 'webdrivers/chromedriver' -require 'webdrivers/geckodriver' require 'gitlab_handbook' @@ -18,8 +16,6 @@ module QA CAPYBARA_MAX_WAIT_TIME = Env.max_capybara_wait_time DEFAULT_WINDOW_SIZE = '1480,2200' - PHONE_VIDEO_SIZE = '500x900' - TABLET_VIDEO_SIZE = '800x1100' def self.blank_page? ['', 'about:blank', 'data:,'].include?(Capybara.current_session.driver.browser.current_url) @@ -46,8 +42,7 @@ module QA new.visit(address, page_class, &block) end - # rubocop: disable Metrics/AbcSize - def self.configure! + def self.configure! # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity return if @configured RSpec.configure do |config| @@ -64,45 +59,31 @@ module QA end Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i - Capybara.register_driver QA::Runtime::Env.browser do |app| - capabilities = Selenium::WebDriver::Remote::Capabilities.send(QA::Runtime::Env.browser) + webdriver_options = Selenium::WebDriver::Options.send(QA::Runtime::Env.browser) case QA::Runtime::Env.browser when :chrome - capabilities['acceptInsecureCerts'] = true if QA::Runtime::Env.accept_insecure_certs? - - # set logging preferences - # this enables access to logs with `page.driver.manage.get_log(:browser)` - capabilities['goog:loggingPrefs'] = { - browser: 'ALL', - client: 'ALL', - driver: 'ALL', - server: 'ALL' - } - # Chrome won't work properly in a Docker container in sandbox mode - capabilities['goog:chromeOptions'] = { - args: %w[no-sandbox] - } + chrome_options = { args: %w[no-sandbox] } # Run headless by default unless WEBDRIVER_HEADLESS is false if QA::Runtime::Env.webdriver_headless? - capabilities['goog:chromeOptions'][:args] << 'headless' + chrome_options[:args] << 'headless' # Chrome documentation says this flag is needed for now # https://developers.google.com/web/updates/2017/04/headless-chrome#cli - capabilities['goog:chromeOptions'][:args] << 'disable-gpu' + chrome_options[:args] << 'disable-gpu' end # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab/issues/4252 - capabilities['goog:chromeOptions'][:args] << 'disable-dev-shm-usage' if QA::Runtime::Env.disable_dev_shm? + chrome_options[:args] << 'disable-dev-shm-usage' if QA::Runtime::Env.disable_dev_shm? # Set chrome default download path # TODO: Set for remote grid as well once Sauce Labs tests are deprecated and Options.chrome is added # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112258 unless QA::Runtime::Env.remote_grid - capabilities['goog:chromeOptions'][:prefs] = { + chrome_options[:prefs] = { 'download.default_directory' => File.expand_path(QA::Runtime::Env.chrome_default_download_path), 'download.prompt_for_download' => false } @@ -111,16 +92,16 @@ module QA # Specify the user-agent to allow challenges to be bypassed # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938 unless QA::Runtime::Env.user_agent.blank? - capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}" + chrome_options[:args] << "user-agent=#{QA::Runtime::Env.user_agent}" end if QA::Runtime::Env.remote_mobile_device_name - capabilities['platformName'] = 'Android' - capabilities['appium:automationName'] = 'UiAutomator2' - capabilities['appium:deviceName'] = QA::Runtime::Env.remote_mobile_device_name - capabilities['appium:platformVersion'] = 'latest' + webdriver_options.platform_name = 'Android' + webdriver_options.add_option('appium:automationName', 'UiAutomator2') + webdriver_options.add_option('appium:deviceName', QA::Runtime::Env.remote_mobile_device_name) + webdriver_options.add_option('appium:platformVersion', 'latest') else - capabilities['goog:chromeOptions'][:args] << "window-size=#{DEFAULT_WINDOW_SIZE}" + chrome_options[:args] << "window-size=#{DEFAULT_WINDOW_SIZE}" end # Slack tries to open an external URL handler @@ -138,7 +119,7 @@ module QA } default_profile = File.join("#{chrome_profile_location}/Default") - FileUtils.mkdir_p(default_profile) unless Dir.exist?(default_profile) + FileUtils.mkdir_p(default_profile) preferences = slack_default_preference # mutate the preferences if it exists @@ -152,56 +133,61 @@ module QA end File.write("#{default_profile}/Preferences", preferences.to_json) - append_chrome_profile_to_capabilities(capabilities) + append_chrome_profile_to_capabilities(chrome_options) end + # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true. + # Useful to speed up local QA. + append_chrome_profile_to_capabilities(chrome_options) if QA::Runtime::Env.reuse_chrome_profile? + + webdriver_options.args = chrome_options[:args] + webdriver_options.prefs = chrome_options[:prefs] + webdriver_options.accept_insecure_certs = true if QA::Runtime::Env.accept_insecure_certs? + # set logging preferences + # this enables access to logs with `page.driver.manage.get_log(:browser)` + webdriver_options.logging_prefs = { + browser: 'ALL', + client: 'ALL', + driver: 'ALL', + server: 'ALL' + } when :safari if QA::Runtime::Env.remote_mobile_device_name - capabilities['platformName'] = 'iOS' - capabilities['appium:automationName'] = 'XCUITest' - capabilities['appium:deviceName'] = QA::Runtime::Env.remote_mobile_device_name - capabilities['appium:platformVersion'] = 'latest' + webdriver_options.platform_name = 'iOS' + webdriver_options.add_option('appium:automationName', 'XCUITest') + webdriver_options.add_option('appium:deviceName', QA::Runtime::Env.remote_mobile_device_name) + webdriver_options.add_option('appium:platformVersion', 'latest') end - when :firefox - capabilities['acceptInsecureCerts'] = true if QA::Runtime::Env.accept_insecure_certs? - + webdriver_options.add_option('acceptInsecureCerts', true) if QA::Runtime::Env.accept_insecure_certs? when :edge - capabilities['ms:edgeOptions'] = { args: ["--window-size=#{DEFAULT_WINDOW_SIZE}"] } + webdriver_options.args << "--window-size=#{DEFAULT_WINDOW_SIZE}" end - # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true. - # Useful to speed up local QA. - append_chrome_profile_to_capabilities(capabilities) if QA::Runtime::Env.reuse_chrome_profile? - selenium_options = { browser: QA::Runtime::Env.browser, - clear_local_storage: true, - capabilities: capabilities + clear_local_storage: true } if QA::Runtime::Env.remote_grid selenium_options[:browser] = :remote selenium_options[:url] = QA::Runtime::Env.remote_grid - capabilities[:browserVersion] = QA::Runtime::Env.browser_version + webdriver_options.browser_version = QA::Runtime::Env.selenoid_browser_version end if QA::Runtime::Env.remote_tunnel_id - capabilities['sauce:options'] = { tunnelIdentifier: QA::Runtime::Env.remote_tunnel_id } + webdriver_options.add_option('sauce:options', { + tunnelIdentifier: QA::Runtime::Env.remote_tunnel_id + }) end if QA::Runtime::Env.record_video? - capabilities['selenoid:options'] = { - enableVideo: true, - videoScreenSize: video_screen_size, - videoName: "#{QA::Runtime::Env.browser}-#{QA::Runtime::Env.browser_version}-#{Time.now}.mp4" - } + webdriver_options.add_option('selenoid:options', { + enableVideo: true + }) end - Capybara::Selenium::Driver.new( - app, - **selenium_options - ) + Capybara::Selenium::Driver.new(app, options: webdriver_options, **selenium_options) end # Keep only the screenshots generated from the last failing test suite @@ -241,28 +227,17 @@ module QA @configured = true end - # rubocop: enable Metrics/AbcSize - def self.append_chrome_profile_to_capabilities(capabilities) - return if capabilities['goog:chromeOptions'][:args].include?(chrome_profile_location) + def self.append_chrome_profile_to_capabilities(chrome_options) + return if chrome_options[:args].include?(chrome_profile_location) - capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{chrome_profile_location}" + chrome_options[:args] << "user-data-dir=#{chrome_profile_location}" end def self.chrome_profile_location ::File.expand_path('../../tmp/qa-profile', __dir__) end - def self.video_screen_size - if QA::Runtime::Env.phone_layout? - PHONE_VIDEO_SIZE - elsif QA::Runtime::Env.tablet_layout? - TABLET_VIDEO_SIZE - else - '' - end - end - class Session include Capybara::DSL diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index cde36ba80c4..54f9db65afb 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -86,6 +86,10 @@ module QA ENV['CI_PROJECT_NAME'] end + def schedule_type + ENV['SCHEDULE_TYPE'] + end + def generate_allure_report? enabled?(ENV['QA_GENERATE_ALLURE_REPORT'], default: false) end @@ -203,8 +207,16 @@ module QA ENV['QA_BROWSER'].nil? ? :chrome : ENV['QA_BROWSER'].to_sym end - def browser_version - ENV['QA_BROWSER_VERSION'] || 'latest' + def selenoid_browser_version + ENV['QA_SELENOID_BROWSER_VERSION'] + end + + def selenoid_browser_image + ENV['QA_SELENOID_BROWSER_IMAGE'] + end + + def video_recorder_image + ENV['QA_VIDEO_RECORDER_IMAGE'] end def remote_mobile_device_name @@ -237,6 +249,19 @@ module QA enabled?(ENV['QA_RECORD_VIDEO'], default: false) end + def use_selenoid? + enabled?(ENV['USE_SELENOID'], default: false) + end + + def require_video_variables! + docs_link = 'https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/running_against_remote_grid.md#testing-with-selenoid' + use_selenoid? || (raise ArgumentError, "USE_SELENOID is required! See docs: #{docs_link}") + remote_grid || (raise ArgumentError, "QA_REMOTE_GRID is required! See docs: #{docs_link}") + video_recorder_image || (raise ArgumentError, "QA_VIDEO_RECORDER_IMAGE is required! See docs: #{docs_link}") + selenoid_browser_image || (raise ArgumentError, "QA_SELENOID_BROWSER_IMAGE is required! See docs: #{docs_link}") + selenoid_browser_version || (raise ArgumentError, "QA_SELENOID_BROWSER_VERSION is required! See docs: #{docs_link}") + end + def user_username ENV['GITLAB_USERNAME'] end @@ -569,7 +594,7 @@ module QA end def super_sidebar_enabled? - enabled?(ENV['QA_SUPER_SIDEBAR_ENABLED'], default: false) + enabled?(ENV['QA_SUPER_SIDEBAR_ENABLED'], default: true) end def require_slack_env! diff --git a/qa/qa/scenario/test/instance/packages.rb b/qa/qa/scenario/test/instance/packages.rb deleted file mode 100644 index 7648a2b5e80..00000000000 --- a/qa/qa/scenario/test/instance/packages.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module QA - module Scenario - module Test - module Instance - class Packages < All - tags :packages - end - end - end - end -end diff --git a/qa/qa/scenario/test/instance/review_blocking.rb b/qa/qa/scenario/test/instance/review_blocking.rb index cb1b6c9cf10..41e760baf45 100644 --- a/qa/qa/scenario/test/instance/review_blocking.rb +++ b/qa/qa/scenario/test/instance/review_blocking.rb @@ -6,9 +6,9 @@ module QA module Instance class ReviewBlocking < All tags :reliable, - :sanity_feature_flags, - :"~orchestrated", - :"~skip_signup_disabled" + :sanity_feature_flags, + :"~orchestrated", + :"~skip_signup_disabled" end end end diff --git a/qa/qa/scenario/test/instance/review_non_blocking.rb b/qa/qa/scenario/test/instance/review_non_blocking.rb index e295171eb0b..7c1b27617d7 100644 --- a/qa/qa/scenario/test/instance/review_non_blocking.rb +++ b/qa/qa/scenario/test/instance/review_non_blocking.rb @@ -6,9 +6,9 @@ module QA module Instance class ReviewNonBlocking < All tags :"~reliable", - :"~smoke", - :"~skip_signup_disabled", - *Specs::Runner::DEFAULT_SKIPPED_TAGS.map { |tag| :"~#{tag}" } + :"~smoke", + :"~skip_signup_disabled", + *Specs::Runner::DEFAULT_SKIPPED_TAGS.map { |tag| :"~#{tag}" } end end end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb index ce558849abd..1a25aeb4c19 100644 --- a/qa/qa/service/docker_run/base.rb +++ b/qa/qa/service/docker_run/base.rb @@ -39,19 +39,20 @@ module QA end def network - shell "docker network inspect #{@network}" - rescue CommandError - 'bridge' - else - @network + network_exists?(@network) ? @network : 'bridge' end def runner_network - shell "docker network inspect #{@runner_network}" - rescue CommandError - network - else - @runner_network + network_exists?(@runner_network) ? @runner_network : network + end + + def inspect_network(name) + shell("docker network inspect #{name}", fail_on_exception: false, return_exit_status: true) + end + + def network_exists?(name) + _, status = inspect_network(name) + status == 0 end def pull diff --git a/qa/qa/service/docker_run/video.rb b/qa/qa/service/docker_run/video.rb new file mode 100644 index 00000000000..c75280b1282 --- /dev/null +++ b/qa/qa/service/docker_run/video.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class Video < Base + class << self + include Service::Shellout + + PHONE_VIDEO_SIZE = '500x900' + TABLET_VIDEO_SIZE = '800x1100' + DESKTOP_VIDEO_SIZE = '1920x1080' + + def configure! + return unless QA::Runtime::Env.record_video? + + begin + QA::Runtime::Env.require_video_variables! + rescue ArgumentError => e + return QA::Runtime::Logger.warn("Aborting video recording setup! Missing variables: #{e}") + end + + @recorder_container_name = get_container_name(QA::Runtime::Env.video_recorder_image) + @browser_image_version = + "#{QA::Runtime::Env.selenoid_browser_image}:#{QA::Runtime::Env.selenoid_browser_version}" + @recorder_container_cmd = "docker exec -d #{@recorder_container_name} sh -c".freeze + set_browser_container_hostname + set_video_screen_size + + if @recorder_container_name.present? && @browser_container_hostname + configure_rspec + + QA::Runtime::Logger.info("Test failure video recording setup complete!") + else + QA::Runtime::Logger.warn("Test failure video recording setup failed!") + end + end + + def start_recording(example) + @current_recording_name = create_recording_name(example) + + ffmpeg_cmd = <<~CMD.tr("\n", ' ') + ffmpeg -y + -f x11grab + -video_size #{@video_size} + -r 15 + -i #{@browser_container_hostname}:99 + -vcodec 'libx264' + -pix_fmt 'yuv420p' + "/data/#{@current_recording_name}.mp4" + CMD + + begin + shell("#{@recorder_container_cmd} '#{ffmpeg_cmd}'") + rescue StandardError => e + QA::Runtime::Logger.warn("Video recording start error: #{e}") + end + end + + def stop_recording + shell("#{@recorder_container_cmd} 'pkill -INT -f ffmpeg'") + rescue StandardError => e + QA::Runtime::Logger.warn("Video recording stop error: #{e}") + end + + def delete_video + shell("#{@recorder_container_cmd} 'rm /data/#{@current_recording_name}.mp4'") + rescue StandardError => e + QA::Runtime::Logger.warn("Video deletion error: #{e}") + end + + private + + def configure_rspec + RSpec.configure do |config| + config.prepend_before do |example| + QA::Service::DockerRun::Video.start_recording(example) + end + + config.append_after do |example| + QA::Service::DockerRun::Video.stop_recording + QA::Service::DockerRun::Video.delete_video unless example.exception + end + end + end + + def create_recording_name(example) + test_name = example.full_description.downcase.parameterize(separator: "_")[0..56] + test_time = Time.now.strftime "%Y-%m-%d-%H-%M-%S-%L" + + "#{test_name}-#{test_time}" + end + + def get_container_name(image) + name = shell("docker ps -f ancestor='#{image}' --format '{{.Names}}'") + + QA::Runtime::Logger.warn("Getting container name for #{image} failed!") if name.empty? + + name + end + + def set_browser_container_hostname + container_name = get_container_name(@browser_image_version) + @browser_container_hostname = shell("docker inspect #{container_name} --format '{{.Config.Hostname}}'") + rescue StandardError => e + QA::Runtime::Logger.warn("Video recording browser container setup error: #{e}") + end + + def set_video_screen_size + @video_size = + if QA::Runtime::Env.phone_layout? + PHONE_VIDEO_SIZE + elsif QA::Runtime::Env.tablet_layout? + TABLET_VIDEO_SIZE + else + DESKTOP_VIDEO_SIZE + end + end + end + end + end + end +end diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index 57f5310901b..684b18a97a7 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -198,42 +198,6 @@ module QA destination_storage[:type] == :praefect ? verify_storage_move_to_praefect(repo_path, destination_storage[:name]) : verify_storage_move_to_gitaly(repo_path, destination_storage[:name]) end - def praefect_sql_ping_healthy? - cmd = "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'" - wait_until_shell_command(cmd) do |line| - QA::Runtime::Logger.debug(line.chomp) - break line.include?('praefect sql-ping: OK') - end - end - - def wait_for_dial_nodes_successful - Support::Waiter.repeat_until(max_attempts: 3, max_duration: 120, sleep_interval: 1) do - nodes_confirmed = { - @primary_node => false, - @secondary_node => false, - @tertiary_node => false - } - - nodes_confirmed.each_key do |node| - nodes_confirmed[node] = true if praefect_dial_nodes_status?(node) - end - - nodes_confirmed.values.all? - end - end - - def praefect_dial_nodes_status?(node, expect_healthy = true) - cmd = "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes -timeout 1s'" - if expect_healthy - wait_until_shell_command_matches(cmd, /SUCCESS: confirmed Gitaly storage "#{node}" in virtual storages \[#{@virtual_storage}\] is served/) - else - wait_until_shell_command(cmd, raise_on_failure: false) do |line| - QA::Runtime::Logger.debug(line.chomp) - break true if line.include?('the following nodes are not healthy') && line.include?(node) - end - end - end - def praefect_dataloss_information(project_id) dataloss_info = [] cmd = "docker exec #{@praefect} praefect -config /var/opt/gitlab/praefect/config.toml dataloss --partially-unavailable=true" diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 218d5eecc85..c825793cc3c 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -11,9 +11,10 @@ module QA module_function - def shell(command, stdin_data: nil, fail_on_exception: true, stream_progress: true, mask_secrets: []) # rubocop:disable Metrics/CyclomaticComplexity + def shell(command, stdin_data: nil, fail_on_exception: true, stream_progress: true, mask_secrets: [], return_exit_status: false) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity cmd_string = Array(command).join(' ') cmd_output = '' + exit_status = 0 QA::Runtime::Logger.info("Executing: `#{mask_secrets_on_string(cmd_string, mask_secrets).cyan}`") @@ -36,7 +37,9 @@ module QA # add newline after progress dots puts if print_progress_dots && !cmd_output.empty? - if wait.value.exited? && wait.value.exitstatus.nonzero? && fail_on_exception + exit_status = wait.value.exitstatus if wait.value.exited? + + if exit_status.nonzero? && fail_on_exception Runtime::Logger.error("Command output:\n#{cmd_output.strip}") unless cmd_output.empty? raise CommandError, "Command: `#{mask_secrets_on_string(cmd_string, mask_secrets)}` failed! ✘" end @@ -44,7 +47,7 @@ module QA Runtime::Logger.debug("Command output:\n#{cmd_output.strip}") unless cmd_output.empty? end - cmd_output.strip + return_exit_status ? [cmd_output.strip, exit_status] : cmd_output.strip end def sql_to_docker_exec_cmd(sql, username, password, database, host, container) diff --git a/qa/qa/specs/features/api/12_systems/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/changing_repository_storage_spec.rb index 01c50c0cd6a..425ab0fa162 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/changing_repository_storage_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/changing_repository_storage_spec.rb @@ -30,7 +30,7 @@ module QA end context 'when moving from one Gitaly storage to another', :orchestrated, :repository_storage, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347827' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347827' do let(:source_storage) { { type: :gitaly, name: 'default' } } let(:destination_storage) { { type: :gitaly, name: QA::Runtime::Env.additional_repository_storage } } let(:project) do @@ -52,7 +52,7 @@ module QA # scenario with other tests that aren't considered orchestrated. # It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828' do let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } } let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } } let(:project) do @@ -75,7 +75,7 @@ module QA # scenario with other tests that aren't considered orchestrated. # It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage context 'when moving from Gitaly Cluster to Gitaly', :requires_praefect, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369204' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369204' do let(:source_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } } let(:destination_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } } let(:project) do diff --git a/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb index 60ce2a65fd1..a3d0fc7f72f 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb @@ -28,8 +28,7 @@ module QA praefect_manager.query_read_distribution.each_with_index do |data, index| pre_read_count = praefect_manager.value_for_node(pre_read_data, data[:node]) QA::Runtime::Logger.debug("Node: #{data[:node]}; before: #{pre_read_count}; now: #{data[:value]}") - expect(data[:value]).to be > pre_read_count, - "Read counts did not differ for node #{data[:node]}" + expect(data[:value]).to be > pre_read_count, "Read counts did not differ for node #{data[:node]}" end end end @@ -45,7 +44,7 @@ module QA end it 'does not read from the unhealthy node', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347834' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347834' do pre_read_data = praefect_manager.query_read_distribution read_from_project(project, number_of_reads_per_loop * 10) diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb deleted file mode 100644 index bd00b3781f7..00000000000 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_connectivity_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Systems' do - describe 'Praefect connectivity commands', :orchestrated, :gitaly_cluster, product_group: :gitaly do - praefect_manager = Service::PraefectManager.new - - before do - praefect_manager.start_all_nodes - end - - context 'in a healthy environment' do - it 'confirms healthy connection to database', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349937' do - expect(praefect_manager.praefect_sql_ping_healthy?).to be true - end - - it 'confirms healthy connection to gitaly nodes', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349938' do - expect(praefect_manager.wait_for_dial_nodes_successful).to be true - end - end - - context 'in an unhealthy environment' do - it 'diagnoses unhealthy connection to database', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349939' do - praefect_manager.stop_node(praefect_manager.postgres) - expect(praefect_manager.praefect_sql_ping_healthy?).to be false - end - - it 'diagnoses connection issues to gitaly nodes', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349940' do - praefect_manager.stop_node(praefect_manager.primary_node) - praefect_manager.stop_node(praefect_manager.tertiary_node) - expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.primary_node, false)).to be true - expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.secondary_node)).to be true - expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.tertiary_node, false)).to be true - - praefect_manager.stop_node(praefect_manager.secondary_node) - expect(praefect_manager.praefect_dial_nodes_status?(praefect_manager.secondary_node, false)).to be true - end - end - end - end -end diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb index cf387c14006..7b9164c1fc9 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/praefect_dataloss_spec.rb @@ -17,14 +17,14 @@ module QA end it 'confirms that changes are synced across all storages', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352691' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352691' do expect { praefect_manager.praefect_dataloss_information(project.id) } - .to(eventually_include('All repositories are fully available on all assigned storages!') - .within(max_duration: 60)) + .to(eventually_include('All repositories are fully available on all assigned storages!') + .within(max_duration: 60)) end it 'identifies how many changes are not in sync across storages', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352692' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352692' do # Ensure our test repository is replicated and in a consistent state prior to test praefect_manager.wait_for_project_synced_across_all_storages(project.id) @@ -59,7 +59,7 @@ module QA end it 'allows admin resolve scenario where data cannot be recovered', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352708' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352708' do # Ensure everything is in sync before begining test praefect_manager.wait_for_project_synced_across_all_storages(project.id) diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb index f88372c0b59..88e746bfefe 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/praefect_replication_queue_spec.rb @@ -23,7 +23,7 @@ module QA end it 'allows replication of different repository after interruption', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347829' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347829' do # We want to fill the replication queue with 10 `in_progress` jobs, # while a lock has been acquired, which is when the problem occurred # as reported in https://gitlab.com/gitlab-org/gitaly/-/issues/2801 diff --git a/qa/qa/specs/features/api/12_systems/gitaly/praefect_repo_sync_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/praefect_repo_sync_spec.rb index 40fc6bf2637..4f916300ee3 100644 --- a/qa/qa/specs/features/api/12_systems/gitaly/praefect_repo_sync_spec.rb +++ b/qa/qa/specs/features/api/12_systems/gitaly/praefect_repo_sync_spec.rb @@ -27,7 +27,7 @@ module QA end it 'allows admin to manage difference between praefect database and disk state', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347606' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347606' do # Some repos are on disk that praefect is not aware of untracked_repositories = praefect_manager.list_untracked_repositories expect(untracked_repositories).to include(repo1) @@ -77,7 +77,7 @@ module QA end it 'allows admin to control the number of replicas of data', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347566' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347566' do praefect_manager .track_repository_in_praefect(repo1['relative_path'], repo1['storage'], repo1['virtual_storage']) 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 53c81b0e187..783c3a65c7d 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 @@ -112,10 +112,10 @@ module QA let(:disabled_after) { 4 } it 'hook is auto-disabled', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389595', quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393274', - type: :investigating - } do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389595', quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393274', + type: :investigating + } do Resource::ProjectWebHook.setup(fail_mock, session: session, issues: true) do |webhook, smocker| hook_trigger_times.times do Resource::Issue.fabricate_via_api! do |issue_init| 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 b74ac89917f..0c94d6e64d5 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 @@ -6,7 +6,7 @@ module QA include_context 'with gitlab project migration' # this spec is used as a sanity test for gitlab migration because it can run outside of orchestrated setup - context 'with import within same instance', orchestrated: false, import: false do + context 'with import within same instance', :skip_live_env, orchestrated: false, import: false do let!(:source_project_with_readme) { true } let!(:source_gitlab_address) { Runtime::Scenario.gitlab_address } let!(:source_admin_api_client) { admin_api_client } diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb index a211eb6042d..d72672e2104 100644 --- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -6,7 +6,7 @@ require 'digest' module QA RSpec.describe 'Create' do describe 'Compare archives of different user projects with the same name and check they\'re different', - product_group: :source_code do + product_group: :source_code do include Support::API let(:project_name) { "project-archive-download-#{SecureRandom.hex(8)}" } diff --git a/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb b/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb index b34b4208337..700cf69da80 100644 --- a/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb @@ -19,14 +19,13 @@ module QA end it 'returns a custom server hook error', - :skip_live_env, - except: { job: 'review-qa-*' }, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369053' do + :skip_live_env, + except: { job: 'review-qa-*' }, + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369053' do expect { project.create_repository_tag('v1.2.3') } - .to raise_error - .with_message( - /rejecting prereceive hook for projects with GL_PROJECT_PATH matching pattern reject-prereceive/ - ) + .to raise_error.with_message( + /rejecting prereceive hook for projects with GL_PROJECT_PATH matching pattern reject-prereceive/ + ) end end end diff --git a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb index c66bd16afe9..79974828a2b 100644 --- a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb +++ b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb @@ -70,15 +70,13 @@ module QA def expect_downstream_pipeline_to_inherit_variable pipeline = downstream_pipeline(upstream_project, 'child1_trigger') expect(pipeline).to have_variable(key: key, value: value), - "Expected to find `{key: 'TEST_VAR', value: 'This is great!'}`" \ - " but got #{pipeline.pipeline_variables}" + "Expected to find `{key: 'TEST_VAR', value: 'This is great!'}` but got #{pipeline.pipeline_variables}" end def expect_downstream_pipeline_not_to_inherit_variable(project, bridge_name) pipeline = downstream_pipeline(project, bridge_name) expect(pipeline).not_to have_variable(key: key, value: value), - "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \ - " but got #{pipeline.pipeline_variables}" + "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}` but got #{pipeline.pipeline_variables}" end end end diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb index 0264b8b1ff2..d5073863dff 100644 --- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb @@ -3,11 +3,12 @@ require 'airborne' module QA - RSpec.describe 'Package', :reliable, only: { subdomain: %i[staging staging-canary pre] }, product_group: :container_registry do + RSpec.describe 'Package', only: { subdomain: %i[staging staging-canary pre] }, + product_group: :container_registry do include Support::API include Support::Helpers::MaskToken - describe 'Container Registry' do + describe 'SaaS Container Registry API' do let(:api_client) { Runtime::API::Client.new(:gitlab) } let(:project) do @@ -43,10 +44,10 @@ module QA - test build: - image: docker:19.03.12 + image: docker:24.0.1 stage: build services: - - docker:19.03.12-dind + - docker:24.0.1-dind variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG DOCKER_HOST: tcp://docker:2376 @@ -76,26 +77,32 @@ module QA YAML end - after do - registry&.remove_via_api! - end - - it 'pushes, pulls image to the registry and deletes tag', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348001' do - Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + it 'pushes, pulls image to the registry and deletes tag', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348001' do + Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2, message: "Commit push") do Resource::Repository::Commit.fabricate_via_api! do |commit| commit.api_client = api_client commit.commit_message = 'Add .gitlab-ci.yml' commit.project = project commit.add_files([{ - file_path: '.gitlab-ci.yml', - content: gitlab_ci_yaml - }]) + file_path: '.gitlab-ci.yml', + content: gitlab_ci_yaml + }]) end end - Support::Waiter.wait_until(max_duration: 10) { pipeline_is_triggered? } - - Support::Retrier.retry_until(max_duration: 300, sleep_interval: 5) do + Support::Retrier.retry_until( + max_duration: 10, + sleep_interval: 1, + message: "Waiting for pipeline to start" + ) do + pipeline_is_triggered? + end + Support::Retrier.retry_until( + max_duration: 300, + sleep_interval: 5, + message: "Waiting for pipeline to succeed" + ) do latest_pipeline_succeed? end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb index 913317afc70..63c57ad455b 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb @@ -49,7 +49,7 @@ module QA end it 'integrates and displays build status for MR pipeline in GitLab', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347788' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347788' do setup_project_integration jenkins_integration = project.find_integration('jenkins') diff --git a/qa/qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb index f6f4a6b3786..1bd819ad87d 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb @@ -38,7 +38,7 @@ module QA end it 'closes an issue via pushing a commit', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347794' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347794' do issue_key = Vendor::Jira::JiraAPI.perform do |jira_api| jira_api.create_issue(jira_project_key) end @@ -49,7 +49,7 @@ module QA end it 'closes an issue via a merge request', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347795' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347795' do issue_key = Vendor::Jira::JiraAPI.perform do |jira_api| jira_api.create_issue(jira_project_key) end diff --git a/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb new file mode 100644 index 00000000000..f5e33cb5b1e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/integrations/slash_commands_spec.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create', only: { subdomain: "staging-ref" } do + describe 'Slack app integration', :slack, product_group: :import_and_integrate do + context 'when using Slash commands' do + # state to be seeded in the Slack UI + let(:title) { "Issue - #{SecureRandom.hex(5)}" } + let(:description) { "Description - #{SecureRandom.hex(6)}" } + + # state to be used in the GitLab API + let(:project_name) { "project_with_slack" } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = project_name + project.initialize_with_readme = true + end + end + + before(:context) do + Runtime::Env.require_slack_env! + end + + before do + authenticate_slack + + Flow::Login.sign_in_unless_signed_in + Flow::Integrations::Slack.start_slack_install(project) + + with_slack_tab do + break if Flow::Integrations::Slack.start_gitlab_connect(project, channel: 'test') + + with_tab(2) do + Page::Profile::ChatNames::New.perform(&:authorize) + end + close_tab(2) + end + end + + after do + project.remove_via_api! + end + + it 'creates an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377890' do + with_slack_tab do + ::Slack::Page::Chat.perform do |chat_page| + chat_page.create_issue(project, channel: 'test', title: title, description: description) + end + + aggregate_failures do + expect { project.issues.size }.to eventually_be(1).within(max_duration: 10) + + issue = project.issues.last + + expect(issue.dig(:author, :username)).to eql(Runtime::User.username) + expect(issue[:title]).to eql(title) + expect(issue[:description]).to eql(description) + end + end + end + + context 'with gitlab issue' do + let!(:issue) do + Resource::Issue.fabricate_via_api! do |issue| + issue.project = project + end + end + + let(:comment) { "Comment #{SecureRandom.hex(6)}" } + + it 'displays an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377891' do + with_slack_tab do + ::Slack::Page::Chat.perform do |chat_page| + chat_page.show_issue(project, channel: 'test', id: issue.iid) + + expect { chat_page.browser.text }.to eventually_include(issue.title).within(max_duration: 10) + end + end + end + + it 'closes an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377892' do + with_slack_tab do + ::Slack::Page::Chat.perform do |chat_page| + chat_page.close_issue(project, channel: 'test', id: issue.iid) + end + + expect { issue.state_events.last&.dig(:state) }.to eventually_eq('closed').within(max_duration: 10) + end + end + + it 'comments on an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377893' do + with_slack_tab do + ::Slack::Page::Chat.perform do |chat_page| + chat_page.comment_on_issue(project, channel: 'test', id: issue.iid, comment: comment) + end + + expect { issue.comments.size }.to eventually_be(1).within(max_duration: 10) + expect(issue.comments.first&.dig(:body)).to eql(comment), "Comments don't match: #{issue.comments}" + end + end + + context 'with target project' do + let(:target) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'target_slack_project' + project.initialize_with_readme = true + end + end + + after do + target.remove_via_api! + end + + it 'moves an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377894' do + with_slack_tab do + ::Slack::Page::Chat.perform do |chat_page| + chat_page.move_issue(project, target, channel: 'test', id: issue.iid) + end + + expect { target.issues.size }.to eventually_be(1).within(max_duration: 10) + + target_issue = target.issues.first + + expect(target_issue&.dig(:title)).to eql(issue.title) + expect(target_issue&.dig(:description)).to eql(issue.description) + end + end + end + end + + private + + def wait_until(timeout = 15, &block) + Support::Waiter.wait_until(max_duration: timeout, reload_page: false, raise_on_failure: false, &block) + end + + def with_slack_tab + switch_to_tab(1) + yield + switch_to_tab(0) + end + + def with_tab(idx) + switch_to_tab(idx) + page.refresh + yield + end + + def close_tab(idx) + page.windows[idx].close + end + + def switch_to_tab(idx) + browser.switch_to.window(browser.window_handles[idx]) + end + + def authenticate_slack + page.open_new_window + + with_slack_tab do + ::Slack::Page::Login.perform do |slack_page| + slack_page.visit + slack_page.sign_in + end + end + end + + def browser + page.driver.browser + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb new file mode 100644 index 00000000000..b2aa3166b9d --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage', :skip_live_env, requires_admin: 'creates users and instance OAuth application', + product_group: :authentication_and_authorization do + let!(:user) { Resource::User.fabricate_via_api! } + let(:consumer_host) { "http://#{consumer_name}.#{Runtime::Env.running_in_ci? ? 'test' : 'bridge'}" } + let(:instance_oauth_app) do + Resource::InstanceOauthApplication.fabricate! do |application| + application.redirect_uri = redirect_uri + application.scopes = scopes + end + end + + after do + instance_oauth_app.remove_via_api! + remove_gitlab_service(consumer_name) + end + + def run_gitlab_service(name:, app_id:, app_secret:) + Service::DockerRun::Gitlab.new( + image: Runtime::Env.release, + name: name, + omnibus_config: omnibus_configuration(app_id: app_id, app_secret: app_secret)).tap do |gitlab| + gitlab.login + gitlab.pull + gitlab.register! + end + end + + def remove_gitlab_service(name) + Service::DockerRun::Gitlab.new(name: name).remove! + end + + def wait_for_service(service) + Support::Waiter.wait_until(max_duration: 900, sleep_interval: 5, raise_on_failure: true) do + service.health == "healthy" + end + end + + shared_examples 'Instance OAuth Application' do |app_type, testcase| + it "creates #{app_type} application and uses it to login", testcase: testcase do + instance_oauth_app + + Page::Main::Menu.perform(&:sign_out_if_signed_in) + + app_id = instance_oauth_app.application_id + app_secret = instance_oauth_app.application_secret + + consumer_gitlab_service = run_gitlab_service(name: consumer_name, app_id: app_id, app_secret: app_secret) + + wait_for_service(consumer_gitlab_service) + + page.visit consumer_host + + expect(page.driver.current_url).to include(consumer_host) + + Page::Main::Login.perform do |login_page| + login_page.public_send("sign_in_with_gitlab_#{app_type}") + end + + expect(page.driver.current_url).to include(Runtime::Scenario.gitlab_address) + + Flow::Login.sign_in(as: user, skip_page_validation: true) + + expect(page.driver.current_url).to include(consumer_host) + + Page::Dashboard::Welcome.perform do |welcome| + expect(welcome).to have_welcome_title("Welcome to GitLab") + end + end + end + + describe 'OIDC' do + let(:consumer_name) { 'gitlab-oidc-consumer' } + let(:redirect_uri) { "#{consumer_host}/users/auth/openid_connect/callback" } + let(:scopes) { %w[openid profile email] } + + def omnibus_configuration(app_id:, app_secret:) + <<~OMNIBUS + gitlab_rails['initial_root_password']='5iveL\!fe'; + gitlab_rails['omniauth_enabled'] = true; + gitlab_rails['omniauth_allow_single_sign_on'] = true; + gitlab_rails['omniauth_block_auto_created_users'] = false; + gitlab_rails['omniauth_providers'] = [ + { + name: 'openid_connect', + label: 'GitLab OIDC', + args: { + name: 'openid_connect', + scope: ['openid','profile','email'], + response_type: 'code', + issuer: '#{Runtime::Scenario.gitlab_address}', + discovery: false, + uid_field: 'preferred_username', + send_scope_to_token_endpoint: 'false', + client_options: { + identifier: '#{app_id}', + secret: '#{app_secret}', + redirect_uri: '#{consumer_host}/users/auth/openid_connect/callback', + jwks_uri: '#{Runtime::Scenario.gitlab_address}/oauth/discovery/keys', + userinfo_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/userinfo', + token_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/token', + authorization_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/authorize' + } + } + } + ]; + OMNIBUS + end + + # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OIDC idP - OIDC application will + # be created here. + # GitLab instance stood up in docker with address gitlab-oidc-consumer.test (or gitlab-oidc-consumer.bridge) is + # the consumer - The GitLab OIDC Login button will be displayed here. + it_behaves_like 'Instance OAuth Application', :oidc, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/405137' + end + + describe 'OAuth' do + let(:consumer_name) { 'gitlab-oauth-consumer' } + let(:redirect_uri) { "#{consumer_host}/users/auth/gitlab/callback" } + let(:scopes) { %w[read_user] } + + def omnibus_configuration(app_id:, app_secret:) + <<~OMNIBUS + gitlab_rails['initial_root_password']='5iveL\!fe'; + gitlab_rails['omniauth_enabled'] = true; + gitlab_rails['omniauth_allow_single_sign_on'] = true; + gitlab_rails['omniauth_block_auto_created_users'] = false; + gitlab_rails['omniauth_providers'] = [ + { + name: 'gitlab', + label: 'GitLab OAuth', + app_id: '#{app_id}', + app_secret: '#{app_secret}', + args: { + scope: 'read_user', + client_options: { + site: '#{Runtime::Scenario.gitlab_address}' + } + } + } + ]; + OMNIBUS + end + + # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OAuth idP - OAuth application will + # be created here. + # GitLab instance stood up in docker with address gitlab-oauth-consumer.test (or gitlab-oauth-consumer.bridge) is + # the consumer - The GitLab OAuth Login button will be displayed here. + it_behaves_like 'Instance OAuth Application', :oauth, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412111' + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb deleted file mode 100644 index 1be51f40f0e..00000000000 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oidc_with_gitlab_as_idp_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Manage', :skip_live_env, requires_admin: 'creates users and instance OAuth application', - product_group: :authentication_and_authorization do - let!(:user) { Resource::User.fabricate_via_api! } - let(:oidc_consumer_name) { 'gitlab-oidc-consumer' } - let(:oidc_consumer_host) { "http://#{oidc_consumer_name}.#{Runtime::Env.running_in_ci? ? 'test' : 'bridge'}" } - let(:instance_oauth_app) do - Resource::InstanceOauthApplication.fabricate! do |application| - application.redirect_uri = "#{oidc_consumer_host}/users/auth/openid_connect/callback" - application.scopes = %w[openid profile email] - end - end - - after do - instance_oauth_app.remove_via_api! - remove_gitlab_service(oidc_consumer_name) - end - - # The host GitLab instance with address Runtime::Scenario.gitlab_address is the OIDC idP - OIDC application will be - # created here. - # GitLab instance stood up in docker with address gitlab-oidc-consumer.test (or gitlab-oidc-consumer.bridge) is - # the consumer - The GitLab OIDC Login button will be displayed here. - describe 'OIDC' do - it( - 'creates GitLab OIDC application and uses it to login', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/405137', - quarantine: { - only: { pipeline: :nightly }, - type: :investigating, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408317' - } - ) do - instance_oauth_app - - Page::Main::Menu.perform(&:sign_out_if_signed_in) - - app_id = instance_oauth_app.application_id - app_secret = instance_oauth_app.application_secret - - consumer_gitlab_service = run_gitlab_service(name: oidc_consumer_name, app_id: app_id, app_secret: app_secret) - - wait_for_service(consumer_gitlab_service) - - page.visit oidc_consumer_host - - expect(page.driver.current_url).to include(oidc_consumer_host) - - Page::Main::Login.perform(&:sign_in_with_oidc) - - expect(page.driver.current_url).to include(Runtime::Scenario.gitlab_address) - - Flow::Login.sign_in(as: user) - - expect(page.driver.current_url).to include(oidc_consumer_host) - - Page::Dashboard::Welcome.perform do |welcome| - expect(welcome).to have_welcome_title("Welcome to GitLab") - end - end - - def run_gitlab_service(name:, app_id:, app_secret:) - Service::DockerRun::Gitlab.new( - image: Runtime::Env.release, - name: name, - omnibus_config: omnibus_configuration(app_id: app_id, app_secret: app_secret)).tap do |gitlab| - gitlab.login - gitlab.pull - gitlab.register! - end - end - - def remove_gitlab_service(name) - Service::DockerRun::Gitlab.new(name: name).remove! - end - - def wait_for_service(service) - Support::Waiter.wait_until(max_duration: 900, sleep_interval: 5, raise_on_failure: true) do - service.health == "healthy" - end - end - - def omnibus_configuration(app_id:, app_secret:) - <<~OMNIBUS - gitlab_rails['initial_root_password']='5iveL\!fe'; - gitlab_rails['omniauth_enabled'] = true; - gitlab_rails['omniauth_allow_single_sign_on'] = true; - gitlab_rails['omniauth_block_auto_created_users'] = false; - gitlab_rails['omniauth_providers'] = [ - { - name: 'openid_connect', - label: 'GitLab OIDC', - args: { - name: 'openid_connect', - scope: ['openid','profile','email'], - response_type: 'code', - issuer: '#{Runtime::Scenario.gitlab_address}', - discovery: false, - uid_field: 'preferred_username', - send_scope_to_token_endpoint: 'false', - client_options: { - identifier: '#{app_id}', - secret: '#{app_secret}', - redirect_uri: '#{oidc_consumer_host}/users/auth/openid_connect/callback', - jwks_uri: '#{Runtime::Scenario.gitlab_address}/oauth/discovery/keys', - userinfo_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/userinfo', - token_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/token', - authorization_endpoint: '#{Runtime::Scenario.gitlab_address}/oauth/authorize' - } - } - } - ]; - OMNIBUS - end - end - 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 f257f1edbc1..5f31ac412d6 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 @@ -112,6 +112,7 @@ module QA let(:user) do Resource::User.fabricate_via_browser_ui! do |user| + user.email_domain = 'gitlab.com' user.expect_fabrication_success = false end end @@ -122,7 +123,6 @@ module QA after do set_require_admin_approval_after_user_signup(false) - user.remove_via_api! if user end it 'allows user login after approval', diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index fd818c3797b..b2dca4fc312 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -43,7 +43,7 @@ module QA end it 'mentions another user in an issue', -testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347988' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347988' do Page::Project::Issue::Show.perform do |show| at_username = "@#{user.username}" diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb index 54e9d3ed6c8..a7071d5fe1b 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb @@ -16,8 +16,6 @@ module QA end before do - QA::Support::Helpers::ImportSource.enable(%w[gitlab_project]) - Flow::Login.sign_in end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb index 994baea7ad9..3b332e6b9f5 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb @@ -11,8 +11,6 @@ module QA end before do - QA::Support::Helpers::ImportSource.enable(%w[gitlab_project]) - Flow::Login.sign_in merge_request.visit! end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index 866c6a146de..631b4ae099d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -59,7 +59,14 @@ module QA project.visit! end - it 'lists branches correctly after CRUD operations', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347740' do + it( + 'lists branches correctly after CRUD operations', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347740', + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/414026', + type: :stale + } + ) do Page::Project::Menu.perform(&:go_to_repository_branches) expect(page).to have_content(master_branch) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb index 3d68de30d57..82074919ad4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb @@ -3,10 +3,6 @@ module QA RSpec.describe 'Create' do describe 'Branch Rules Overview', product_group: :source_code, - feature_flag: { - name: 'branch_rules', - scope: :project - }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/403583', type: :flaky @@ -22,8 +18,6 @@ module QA end before do - Runtime::Feature.enable(:branch_rules, project: project) - Flow::Login.sign_in Resource::Repository::Commit.fabricate_via_api! do |commit| @@ -35,10 +29,6 @@ module QA end end - after do - Runtime::Feature.disable(:branch_rules, project: project) - end - it 'adds a new branch rule', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/397587' do project.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index 0ec231ed66e..41ef38d2d66 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -4,7 +4,7 @@ module QA RSpec.describe 'Create' do describe 'Git push over HTTP', :smoke, :skip_fips_env, product_group: :source_code do it 'user using a personal access token pushes code to the repository', -testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347749' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347749' do Flow::Login.sign_in access_token = Resource::PersonalAccessToken.fabricate!.token diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb index efa1f9fe2c9..edc85849356 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb @@ -4,7 +4,7 @@ module QA RSpec.describe 'Create' do describe 'Git push over HTTP', product_group: :source_code do it 'user pushes code to the repository', :smoke, :skip_fips_env, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347747' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347747' do Flow::Login.sign_in Resource::Repository::ProjectPush.fabricate! do |push| @@ -20,7 +20,7 @@ module QA end it 'pushes to a project using a specific Praefect repository storage', :smoke, :skip_fips_env, :requires_admin, - :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347789' do + :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347789' do Flow::Login.sign_in_as_admin project = Resource::Project.fabricate_via_api! do |storage_project| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb index f281f441e8a..dcee723a1c4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb @@ -27,7 +27,7 @@ module QA end it 'pushes code to the repository via SSH', :smoke, :skip_fips_env, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347825' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347825' do Resource::Repository::ProjectPush.fabricate! do |push| push.project = project push.ssh_key = @key @@ -43,7 +43,7 @@ module QA end it 'pushes multiple branches and tags together', :smoke, :skip_fips_env, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347826' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347826' do branches = [] tags = [] Git::Repository.perform do |repository| diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb index c971ab869cd..c88bd808299 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb @@ -66,15 +66,19 @@ module QA end def verify_comment_content(author, comment_content) - Page::Dashboard::Snippet::Show.perform do |comment| - expect(comment).to have_comment_author(author) - expect(comment).to have_comment_content(comment_content) + Page::Dashboard::Snippet::Show.perform do |snippet| + expect(snippet).to have_comment_author(author) + expect(snippet).to have_comment_content(comment_content) end end def verify_comment_deleted - expect(page).not_to have_content(comment_author.username) - expect(page).not_to have_content(edited_comment_content) + Page::Dashboard::Snippet::Show.perform do |snippet| + snippet.within_notes_list do + expect(snippet).not_to have_content(comment_author.username) + expect(snippet).not_to have_content(edited_comment_content) + end + end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb index 435b9321601..9b1df337065 100644 --- a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# tagged transient due to feature-flag caching flakiness. Remove tag along with feature flag removal. + module QA - RSpec.describe 'Create', feature_flag: { name: 'source_editor_toolbar', scope: :global } do + RSpec.describe 'Create' do describe 'Source editor toolbar preview', product_group: :source_code do let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -13,16 +13,11 @@ module QA let(:edited_readme_content) { 'Here is the edited content.' } before do - Runtime::Feature.enable(:source_editor_toolbar) Flow::Login.sign_in end - after do - Runtime::Feature.disable(:source_editor_toolbar) - end - it 'can preview markdown side-by-side while editing', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367749' do + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367749' do project.visit! Page::Project::Show.perform do |project| project.click_file('README.md') @@ -30,16 +25,11 @@ module QA Page::File::Show.perform(&:click_edit) - # wait_until required due to feature_caching. Remove along with feature flag removal. Page::File::Edit.perform do |file| - Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page, - retry_on_exception: true) do - expect(file).to have_element(:editor_toolbar_button) - end file.remove_content - file.click_editor_toolbar file.add_content('# ' + edited_readme_content) - file.wait_for_markdown_preview('h1', edited_readme_content) + file.preview + expect(file.has_markdown_preview?('h1', edited_readme_content)).to be true file.commit_changes end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb index af3d064a37c..3e8f9307cec 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb @@ -42,9 +42,7 @@ module QA Page::Project::WebIDE::VSCode.perform do |ide| ide.wait_for_ide_to_load ide.create_new_folder(directory_name) - ide.within_vscode_editor do - expect(page).to have_content('A file or folder first_directory already exists at this location.') - end + ide.has_message?('A file or folder first_directory already exists at this location.') end end end @@ -61,10 +59,8 @@ module QA Page::Project::WebIDE::VSCode.perform do |ide| ide.wait_for_ide_to_load ide.create_new_folder(directory_name) - ide.commit_and_push(directory_name) - ide.within_vscode_editor do - expect(page).to have_content('No changes found. Not able to commit.') - end + ide.commit_toggle(directory_name) + ide.has_message?('No changes found. Not able to commit.') end end end 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 new file mode 100644 index 00000000000..336d32bcc35 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create', product_group: :ide, + quarantine: { + only: { job: 'slow-network' }, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/387609', + type: :flaky + } do + describe 'Upload a file in Web IDE' do + let(:file_path) { File.absolute_path(File.join('qa', 'fixtures', 'web_ide', file_name)) } + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'upload-file-project' + project.initialize_with_readme = true + end + end + + before do + Flow::Login.sign_in + project.visit! + end + + context 'when a file with the same name already exists' do + let(:file_name) { 'README.md' } + + it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390005' do + Page::Project::Show.perform(&:open_web_ide!) + Page::Project::WebIDE::VSCode.perform do |ide| + ide.upload_file(file_path) + ide.has_message?("A file or folder with the name 'README.md' already exists in the destination folder") + end + end + end + + shared_examples 'upload a file' do + it "verifies it successfully uploads and commits to a MR" do + Page::Project::Show.perform(&:open_web_ide!) + Page::Project::WebIDE::VSCode.perform do |ide| + ide.upload_file(file_path) + ide.has_message?(file_name) + ide.commit_and_push(file_name) + ide.has_message?('Success! Your changes have been committed.') + ide.create_merge_request + end + + # Opens the MR in new tab and verify the file is in the MR + page.driver.browser.switch_to.window(page.driver.browser.window_handles.last) + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_content(file_name) + end + end + end + + context 'when the file is a text file', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390006' do + let(:file_name) { 'text_file.txt' } + + it_behaves_like 'upload a file' + end + + context 'when the file is an image', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390007' do + let(:file_name) { 'dk.png' } + + it_behaves_like 'upload a file' + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb index dc8db7ec387..59591fbe1cd 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb @@ -1,10 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring, feature_flag: { - name: 'ci_batch_project_includes_context', - scope: :global - } do + RSpec.describe 'Verify', :runner, product_group: :pipeline_authoring do describe 'Include multiple files from multiple projects' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } @@ -34,7 +31,7 @@ module QA end end - def before_do + before do Flow::Login.sign_in add_included_files_for(main_project) @@ -50,44 +47,17 @@ module QA runner.remove_via_api! end - context 'when FF is on', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/396374' do - before do - Runtime::Feature.enable(:ci_batch_project_includes_context, project: main_project) - sleep 60 - - before_do - end - - it 'runs the pipeline with composed config' do - Page::Project::Pipeline::Show.perform do |pipeline| - aggregate_failures 'pipeline has all expected jobs' do - expect(pipeline).to have_job('test_for_main') - expect(pipeline).to have_job("test1_for_#{project1.full_path}") - expect(pipeline).to have_job("test1_for_#{project2.full_path}") - expect(pipeline).to have_job("test2_for_#{project1.full_path}") - expect(pipeline).to have_job("test2_for_#{main_project.full_path}") - end - end - end - end - - context 'when FF is off', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/396375' do - before do - Runtime::Feature.disable(:ci_batch_project_includes_context, project: main_project) - sleep 60 - - before_do - end - - it 'runs the pipeline with composed config' do - Page::Project::Pipeline::Show.perform do |pipeline| - aggregate_failures 'pipeline has all expected jobs' do - expect(pipeline).to have_job('test_for_main') - expect(pipeline).to have_job("test1_for_#{project1.full_path}") - expect(pipeline).to have_job("test1_for_#{project2.full_path}") - expect(pipeline).to have_job("test2_for_#{project1.full_path}") - expect(pipeline).to have_job("test2_for_#{main_project.full_path}") - end + it( + 'runs the pipeline with composed config', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/396374' + ) do + Page::Project::Pipeline::Show.perform do |pipeline| + aggregate_failures 'pipeline has all expected jobs' do + expect(pipeline).to have_job('test_for_main') + expect(pipeline).to have_job("test1_for_#{project1.full_path}") + expect(pipeline).to have_job("test1_for_#{project2.full_path}") + expect(pipeline).to have_job("test2_for_#{project1.full_path}") + expect(pipeline).to have_job("test2_for_#{main_project.full_path}") 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 379499662c2..ecf4826262b 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 @@ -1,7 +1,11 @@ # frozen_string_literal: true module QA - RSpec.describe 'Verify', :runner, product_group: :pipeline_execution do + RSpec.describe 'Verify', :runner, product_group: :pipeline_execution, + quarantine: { + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/411510', + type: :flaky + } do context 'when pipeline is blocked' do let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb deleted file mode 100644 index 30c71bc590c..00000000000 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Verify' do - describe 'Pipeline editor', product_group: :pipeline_authoring do - let(:random_test_string) { SecureRandom.hex(10) } - - let(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'pipeline-editor-project' - end - end - - let!(:commit) do - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files( - [ - { - file_path: '.gitlab-ci.yml', - content: <<~YAML - stages: - - test - - initialize: - stage: test - script: - - echo "I am now on branch #{project.default_branch}" - YAML - } - ] - ) - end - end - - let!(:test_branch) do - Resource::Repository::ProjectPush.fabricate! do |resource| - resource.project = project - resource.branch_name = random_test_string - resource.file_name = '.gitlab-ci.yml' - resource.file_content = <<~YAML - stages: - - test - - initialize: - stage: test - script: - - echo "I am now on branch #{random_test_string}" - YAML - end - end - - before do - Flow::Login.sign_in - project.visit! - - # Project push sometimes takes a while to complete - # Making sure new branch is pushed successfully prior to interacting - Support::Retrier.retry_until(max_duration: 15, sleep_interval: 3, reload_page: false, message: 'Ensuring project has branch') do - project.has_branch?(random_test_string) - end - end - - after do - project.remove_via_api! - end - - it 'can switch branches and target branch field updates accordingly', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347661' do - Page::Project::Menu.perform(&:go_to_pipeline_editor) - Page::Project::PipelineEditor::Show.perform do |show| - show.open_branch_selector_dropdown - show.select_branch_from_dropdown(random_test_string) - - aggregate_failures do - expect(show.source_branch_name).to eq(random_test_string), 'Branch field is not showing expected branch name.' - expect(show.editing_content).to have_content(random_test_string), 'Editor content does not include expected test string.' - end - - show.open_branch_selector_dropdown - show.select_branch_from_dropdown(project.default_branch) - - aggregate_failures do - expect(show.source_branch_name).to eq(project.default_branch), 'Branch field is not showing expected branch name.' - expect(show.editing_content).to have_content(project.default_branch), 'Editor content does not include expected test string.' - end - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb index 745879cf12f..a9f46e394f7 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_tabs_spec.rb @@ -103,10 +103,10 @@ module QA show.go_to_full_configuration_tab - # TODO: remove this retry when + # TODO: remove page reload when # https://gitlab.com/gitlab-org/gitlab/-/issues/378536 is resolved - show.retry_until(max_attempts: 2, reload: true, sleep_interval: 1) { show.has_no_alert? } - expect(show).to have_source_editor + expect { show.has_source_editor? } + .to eventually_be_truthy.within(max_attempts: 2, reload_page: show, sleep_interval: 1) expect(show.ci_syntax_validate_message).to have_content('CI configuration is invalid') end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb index aaaa11ef867..c693a57605e 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb @@ -71,10 +71,10 @@ module QA if pull_image expect(job_log).to have_content(message), - "Expected to find #{message} in #{job_log}, but didn't." + "Expected to find #{message} in #{job_log}, but didn't." else expect(job_log).not_to have_content(message), - "Found #{message} in #{job_log}, but didn't expect to." + "Found #{message} in #{job_log}, but didn't expect to." end end end @@ -96,7 +96,7 @@ module QA visit_job expect(job_log).to include(text1, text2), - "Expected to find contents #{text1} and #{text2} in #{job_log}, but didn't." + "Expected to find contents #{text1} and #{text2} in #{job_log}, but didn't." 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 37d1e20111d..d733ac9ba72 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 @@ -142,7 +142,7 @@ module QA end acceptable_statuses = %w[skipped manual] - pipeline.pipeline_jobs.select { |job| !(acceptable_statuses.include? job[:status]) } + pipeline.jobs.select { |job| !(acceptable_statuses.include? job[:status]) } end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/online_garbage_collection_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/online_garbage_collection_spec.rb deleted file mode 100644 index 1e076117e4e..00000000000 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry/online_garbage_collection_spec.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -module QA - RSpec.describe 'Package', :requires_admin, product_group: :container_registry do - describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] } do - let(:group) { Resource::Group.fabricate_via_api! } - - let(:imported_project) do - Resource::ProjectImportedFromURL.fabricate_via_browser_ui! do |project| - project.name = 'container-registry' - project.group = group - project.gitlab_repository_path = 'https://gitlab.com/gitlab-org/container-registry.git' - end - end - - let!(:gitlab_ci_yaml) do - <<~YAML - variables: - GOPATH: $CI_PROJECT_DIR/.go - BUILD_CACHE: $CI_PROJECT_DIR/.online-gc-tester - STAGE_ONE_VALIDATION_DELAY: "6m" - STAGE_TWO_VALIDATION_DELAY: "12m" - STAGE_THREE_VALIDATION_DELAY: "6m" - STAGE_FOUR_VALIDATION_DELAY: "12m" - STAGE_FIVE_VALIDATION_DELAY: "12m" - - stages: - - generate - - build - - test - - .base: &base - image: docker:19 - services: - - docker:19-dind - variables: - DOCKER_HOST: tcp://docker:2376 - DOCKER_TLS_CERTDIR: "/certs" - DOCKER_TLS_VERIFY: 1 - DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" - before_script: - - until docker info; do sleep 1; done - - mkdir -p $GOPATH - - mkdir -p $BUILD_CACHE - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - test: - stage: generate - extends: .base - script: - - apk add go make git - - make binaries - - ./bin/online-gc-tester generate --base-dir=$BUILD_CACHE - - ./bin/online-gc-tester build --base-dir=$BUILD_CACHE - - ./bin/online-gc-tester push --base-dir=$BUILD_CACHE - - ./bin/online-gc-tester pull --base-dir=$BUILD_CACHE - - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=1 --delay=$STAGE_ONE_VALIDATION_DELAY - - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=2 --delay=$STAGE_TWO_VALIDATION_DELAY - - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=3 --delay=$STAGE_THREE_VALIDATION_DELAY - - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=4 --delay=$STAGE_FOUR_VALIDATION_DELAY - - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=5 --delay=$STAGE_FIVE_VALIDATION_DELAY - timeout: 1h 30m - YAML - end - - before do - QA::Support::Helpers::ImportSource.enable('git') - - Flow::Login.sign_in - - imported_project - - Page::Project::Menu.perform(&:go_to_repository_settings) - - Page::Project::Settings::Repository.perform do |setting| - setting.expand_default_branch - end - - Page::Project::Settings::DefaultBranch.perform do |setting| - setting.set_default_branch('online-gc-test-builder-poc') - setting.click_save_changes_button - end - - Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = imported_project - commit.branch = 'online-gc-test-builder-poc' - commit.commit_message = 'Update .gitlab-ci.yml' - commit.update_files([{ - file_path: '.gitlab-ci.yml', - content: gitlab_ci_yaml - }]) - end - end - end - - it 'runs the online garbage collector tool', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347663' do - imported_project.visit! - - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('test') - end - - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 3900) - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/container_registry_spec.rb index 680b722edb7..4b2d9f96cd2 100644 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/container_registry_spec.rb @@ -2,7 +2,8 @@ module QA RSpec.describe 'Package' do - describe 'Container Registry', only: { subdomain: %i[staging staging-canary pre] }, product_group: :container_registry do + describe 'SaaS Container Registry', only: { subdomain: %i[staging staging-canary pre] }, + product_group: :container_registry do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'project-with-registry' @@ -12,7 +13,7 @@ module QA let(:registry_repository) do Resource::RegistryRepository.fabricate! do |repository| - repository.name = "#{project.path_with_namespace}" + repository.name = project.path_with_namespace.to_s repository.project = project end end @@ -20,10 +21,10 @@ module QA let!(:gitlab_ci_yaml) do <<~YAML build: - image: docker:19.03.12 + image: docker:24.0.1 stage: build services: - - docker:19.03.12-dind + - docker:24.0.1-dind variables: IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG DOCKER_HOST: tcp://docker:2376 @@ -49,7 +50,8 @@ module QA registry_repository&.remove_via_api! end - it 'pushes project image to the container registry and deletes tag', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347687' do + it 'pushes project image to the container registry and deletes tag', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412806' do Flow::Login.sign_in project.visit! @@ -58,9 +60,9 @@ module QA commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files([{ - file_path: '.gitlab-ci.yml', - content: gitlab_ci_yaml - }]) + file_path: '.gitlab-ci.yml', + content: gitlab_ci_yaml + }]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb new file mode 100644 index 00000000000..85a88b54cc2 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Package' do + describe 'SaaS Container Registry', only: { subdomain: %i[staging] }, product_group: :container_registry do + let(:project) do + Resource::Project.init do |project| + project.path_with_namespace = 'gitlab-qa/container-registry-sanity' + end.reload! + end + + it 'pulls an image from an existing repository', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412799' do + Flow::Login.sign_in + project.visit! + + Page::Project::Menu.perform(&:go_to_pipelines) + Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button) + Page::Project::Pipeline::New.perform(&:click_run_pipeline_button) + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('test') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end + end + end + end +end 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/self_managed/container_registry_spec.rb index b39475b481d..800b3a01dc6 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/self_managed/container_registry_spec.rb @@ -50,59 +50,59 @@ module QA context "when tls is disabled" do where do { - 'using docker:18.09.9 and a personal access token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:20.10.23 and a personal access token' => { + docker_client_version: 'docker:20.10.23', authentication_token_type: :personal_access_token, token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348499' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412807' }, - 'using docker:18.09.9 and a project deploy token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:20.10.23 and a project deploy token' => { + docker_client_version: 'docker:20.10.23', authentication_token_type: :project_deploy_token, token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348852' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412808' }, - 'using docker:18.09.9 and a ci job token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:20.10.23 and a ci job token' => { + docker_client_version: 'docker:20.10.23', authentication_token_type: :ci_job_token, token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348765' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412809' }, - 'using docker:19.03.12 and a personal access token' => { - docker_client_version: 'docker:19.03.12', + 'using docker:23.0.6 and a personal access token' => { + docker_client_version: 'docker:23.0.6', authentication_token_type: :personal_access_token, token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348507' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412810' }, - 'using docker:19.03.12 and a project deploy token' => { - docker_client_version: 'docker:19.03.12', + 'using docker:23.0.6 and a project deploy token' => { + docker_client_version: 'docker:23.0.6', authentication_token_type: :project_deploy_token, token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348859' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412813' }, - 'using docker:19.03.12 and a ci job token' => { - docker_client_version: 'docker:19.03.12', + 'using docker:23.0.6 and a ci job token' => { + docker_client_version: 'docker:23.0.6', authentication_token_type: :ci_job_token, token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348654' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412814' }, - 'using docker:20.10 and a personal access token' => { - docker_client_version: 'docker:20.10', + 'using docker:24.0.1 and a personal access token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :personal_access_token, token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348754' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412817' }, - 'using docker:20.10 and a project deploy token' => { - docker_client_version: 'docker:20.10', + 'using docker:24.0.1 and a project deploy token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :project_deploy_token, token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348856' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412818' }, - 'using docker:20.10 and a ci job token' => { - docker_client_version: 'docker:20.10', + 'using docker:24.0.1 and a ci job token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :ci_job_token, token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348766' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412819' } } end @@ -201,10 +201,10 @@ module QA content: <<~YAML build: - image: docker:19.03.12 + image: docker:23.0.6 stage: build services: - - name: docker:19.03.12-dind + - name: docker:23.0.6-dind command: - /bin/sh - -c diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb index a0d283fd7ad..235c3604523 100644 --- a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :registry, only: { pipeline: :main }, product_group: :container_registry do + RSpec.describe 'Package', :orchestrated, :registry, :skip_live_env, product_group: :container_registry do describe 'Dependency Proxy' do using RSpec::Parameterized::TableSyntax include Support::Helpers::MaskToken @@ -53,65 +53,28 @@ module QA end after do - project.remove_via_api! runner.remove_via_api! end where do { - 'using docker:18.09.9 and a personal access token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:24.0.1 and a personal access token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :personal_access_token, token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370195' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412820' }, - 'using docker:18.09.9 and a group deploy token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:24.0.1 and a group deploy token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :group_deploy_token, token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370196' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412821' }, - 'using docker:18.09.9 and a ci job token' => { - docker_client_version: 'docker:18.09.9', + 'using docker:24.0.1 and a ci job token' => { + docker_client_version: 'docker:24.0.1', authentication_token_type: :ci_job_token, token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370198' - }, - 'using docker:19.03.12 and a personal access token' => { - docker_client_version: 'docker:19.03.12', - authentication_token_type: :personal_access_token, - token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370189' - }, - 'using docker:19.03.12 and a group deploy token' => { - docker_client_version: 'docker:19.03.12', - authentication_token_type: :group_deploy_token, - token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370190' - }, - 'using docker:19.03.12 and a ci job token' => { - docker_client_version: 'docker:19.03.12', - authentication_token_type: :ci_job_token, - token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370191' - }, - 'using docker:20.10 and a personal access token' => { - docker_client_version: 'docker:20.10', - authentication_token_type: :personal_access_token, - token_name: 'Personal Access Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370192' - }, - 'using docker:20.10 and a group deploy token' => { - docker_client_version: 'docker:20.10', - authentication_token_type: :group_deploy_token, - token_name: 'Deploy Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370193' - }, - 'using docker:20.10 and a ci job token' => { - docker_client_version: 'docker:20.10', - authentication_token_type: :ci_job_token, - token_name: 'Job Token', - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/370194' + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412822' } } end @@ -186,7 +149,7 @@ module QA project.group.visit! - Page::Group::Menu.perform(&:go_to_dependency_proxy) + Page::Group::Menu.perform(&:go_to_group_dependency_proxy) Page::Group::DependencyProxy.perform do |index| expect(index).to have_blob_count("Contains 1 blobs of images") diff --git a/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb index 8d80e314d71..312c5a13a61 100644 --- a/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :requires_admin, :orchestrated, :packages, product_group: :package_registry do + RSpec.describe 'Package', product_group: :package_registry do describe 'Terraform Module Registry' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb index d0bae3cfa98..58eb63de21d 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'Composer Repository' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb index bedd447f6ec..4179b65d930 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage, product_group: :package_registry, quarantine: { + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry, quarantine: { only: { job: 'object_storage' }, issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/335981', type: :bug diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb index c4a1663f36e..1253dc91ca3 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'Generic Repository' do include Runtime::Fixtures 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 6cf6a77ab74..19d0a4a816f 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'Helm Registry' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures 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 f5dff1b3e0b..3553e67eb2c 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage, :reliable, product_group: :package_registry, feature_flag: { name: 'maven_central_request_forwarding', scope: :global } do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'Maven group level endpoint' do include Runtime::Fixtures include Support::Helpers::MaskToken @@ -221,100 +221,5 @@ module QA end end end - - describe 'Maven request forwarding' do - include Runtime::Fixtures - - let(:group_id) { 'com.gitlab.qa' } - let(:artifact_id) { "maven-#{SecureRandom.hex(8)}" } - let(:package_name) { "#{group_id}/#{artifact_id}".tr('.', '/') } - let(:package_version) { '1.3.7' } - let(:personal_access_token) { Runtime::Env.personal_access_token } - let(:group) { Resource::Group.fabricate_via_api! } - - let(:imported_project) do - Resource::ProjectImportedFromURL.fabricate_via_browser_ui! do |project| - project.name = "maven_imported_project" - project.group = group - project.gitlab_repository_path = 'https://gitlab.com/gitlab-org/quality/imported-projects/maven.git' - end - end - - let(:gitlab_address_with_port) do - uri = URI.parse(Runtime::Scenario.gitlab_address) - "#{uri.scheme}://#{uri.host}:#{uri.port}" - end - - let(:package) do - Resource::Package.init do |package| - package.name = package_name - package.project = imported_project - end - end - - let(:runner) do - Resource::ProjectRunner.fabricate! do |runner| - runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = ["runner-for-#{imported_project.group.name}"] - runner.executor = :docker - runner.token = imported_project.group.reload!.runners_token - end - end - - before do - QA::Support::Helpers::ImportSource.enable('git') - - Runtime::Feature.enable(:maven_central_request_forwarding) - Flow::Login.sign_in_unless_signed_in - - imported_project - runner - end - - after do - Runtime::Feature.disable(:maven_central_request_forwarding) - - runner.remove_via_api! - package.remove_via_api! - imported_project.remove_via_api! - end - - it( - 'uses GitLab as a mirror of the central proxy', - :skip_live_env, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/375988', - quarantine: { - type: :investigating, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/378221' - } - ) do - Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do - Resource::Repository::Commit.fabricate_via_api! do |commit| - settings_xml = ERB.new(read_fixture('package_managers/maven/group/consumer/request_forwarding', 'settings.xml.erb')).result(binding) - gitlab_ci_yaml = ERB.new(read_fixture('package_managers/maven/group/consumer/request_forwarding', 'gitlab_ci.yaml.erb')).result(binding) - - commit.project = imported_project - commit.commit_message = 'Add files' - commit.add_files( - [ - { file_path: '.gitlab-ci.yml', content: gitlab_ci_yaml }, - { file_path: 'settings.xml', content: settings_xml } - ]) - end - end - - imported_project.visit! - - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('install') - end - - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - end - end - end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb index 27bca6c17a2..91d8f0dc181 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage, :reliable, - feature_flag: { - name: 'maven_central_request_forwarding', - scope: :global - } do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' } do describe 'Maven project level endpoint', product_group: :package_registry do include Runtime::Fixtures include Support::Helpers::MaskToken @@ -148,109 +144,5 @@ module QA end end end - - describe 'Maven request forwarding' do - include Runtime::Fixtures - - let(:group_id) { 'com.gitlab.qa' } - let(:artifact_id) { "maven-#{SecureRandom.hex(8)}" } - let(:package_name) { "#{group_id}/#{artifact_id}".tr('.', '/') } - let(:package_version) { '1.3.7' } - let(:package_type) { 'maven' } - let(:personal_access_token) { Runtime::Env.personal_access_token } - let(:group) { Resource::Group.fabricate_via_api! } - - let(:gitlab_address_with_port) do - uri = URI.parse(Runtime::Scenario.gitlab_address) - "#{uri.scheme}://#{uri.host}:#{uri.port}" - end - - let(:package) do - Resource::Package.init do |package| - package.name = package_name - package.project = imported_project - end - end - - let(:runner) do - Resource::GroupRunner.fabricate! do |runner| - runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = ["runner-for-#{imported_project.name}"] - runner.executor = :docker - runner.group = group - end - end - - let(:imported_project) do - Resource::ProjectImportedFromURL.fabricate_via_browser_ui! do |project| - project.name = "#{package_type}_imported_project" - project.group = group - project.gitlab_repository_path = 'https://gitlab.com/gitlab-org/quality/imported-projects/maven.git' - end - end - - before do - QA::Support::Helpers::ImportSource.enable('git') - - Runtime::Feature.enable(:maven_central_request_forwarding) - Flow::Login.sign_in_unless_signed_in - - imported_project - runner - end - - after do - Runtime::Feature.disable(:maven_central_request_forwarding) - - runner.remove_via_api! - package.remove_via_api! - imported_project.remove_via_api! - end - - it( - 'uses GitLab as a mirror of the central proxy', - :skip_live_env, - quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/378221', - type: :investigating - }, - testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/375767' - ) 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/project/request_forwarding', - 'gitlab_ci.yaml.erb' - ) - ) - .result(binding) - settings_xml = ERB.new(read_fixture('package_managers/maven/project/request_forwarding', - 'settings.xml.erb' - ) - ) - .result(binding) - - commit.project = imported_project - commit.commit_message = 'Add files' - commit.add_files( - [ - { file_path: '.gitlab-ci.yml', content: gitlab_ci_yaml }, - { file_path: 'settings.xml', content: settings_xml } - ]) - end - end - - imported_project.visit! - - Flow::Pipeline.visit_latest_pipeline - - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('install') - end - - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) - end - end - end end end 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 423304051a2..032c77b2519 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'Maven Repository with Gradle' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb new file mode 100644 index 00000000000..1b97f7d0a6a --- /dev/null +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Package' do + describe 'Package Registry', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do + describe 'npm group level endpoint' do + using RSpec::Parameterized::TableSyntax + include Runtime::Fixtures + include Support::Helpers::MaskToken + + let!(:registry_scope) { Runtime::Namespace.sandbox_name } + let!(:personal_access_token) do + Flow::Login.sign_in unless Page::Main::Menu.perform(&:signed_in?) + + Resource::PersonalAccessToken.fabricate!.token + end + + let(:project_deploy_token) do + Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token| + deploy_token.name = 'npm-deploy-token' + deploy_token.project = project + deploy_token.scopes = %w[ + read_repository + read_package_registry + write_package_registry + ] + end + end + + let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) } + let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" } + let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" } + + let!(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'npm-group-level-publish' + end + end + + let!(:another_project) do + Resource::Project.fabricate_via_api! do |another_project| + another_project.name = 'npm-group-level-install' + another_project.group = project.group + end + end + + let!(:runner) do + Resource::GroupRunner.fabricate! do |runner| + runner.name = "qa-runner-#{Time.now.to_i}" + runner.tags = ["runner-for-#{project.group.name}"] + runner.executor = :docker + runner.group = project.group + end + end + + let(:package) do + Resource::Package.init do |package| + package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}" + package.project = project + end + end + + after do + package.remove_via_api! + runner.remove_via_api! + project.remove_via_api! + another_project.remove_via_api! + end + + where(:case_name, :authentication_token_type, :token_name, :testcase) do + 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413760' + 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413761' + 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/413762' + end + + with_them do + let(:auth_token) do + case authentication_token_type + when :personal_access_token + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project) + use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: another_project) + when :ci_job_token + '${CI_JOB_TOKEN}' + when :project_deploy_token + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project) + use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: another_project) + end + end + + it 'push and pull a npm package via CI', testcase: params[:testcase] do + Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', + 'npm_upload_package_group.yaml.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding) + + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add files' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: npm_upload_yaml + }, + { + file_path: 'package.json', + content: package_json + } + ]) + end + end + + project.visit! + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('deploy') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end + + Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + npm_install_yaml = ERB.new(read_fixture('package_managers/npm', + 'npm_install_package_group.yaml.erb')).result(binding) + + commit.project = another_project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: npm_install_yaml + } + ]) + end + end + + another_project.visit! + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('install') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + job.click_browse_button + end + + Page::Project::Artifact::Show.perform do |artifacts| + artifacts.go_to_directory('node_modules') + artifacts.go_to_directory("@#{registry_scope}") + expect(artifacts).to have_content(project.name.to_s) + end + + project.visit! + Page::Project::Menu.perform(&:go_to_package_registry) + + Page::Project::Packages::Index.perform do |index| + expect(index).to have_package(package.name) + + index.click_package(package.name) + end + + Page::Project::Packages::Show.perform do |show| + expect(show).to have_package_info(package.name, "1.0.0") + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb index 4a8b95717d0..b0702b3f089 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Package' do - describe 'Package Registry', :skip_live_env, :orchestrated, :reliable, :packages, :object_storage, product_group: :package_registry do + describe 'Package Registry', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'npm instance level endpoint' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures @@ -90,7 +90,7 @@ module QA it 'push and pull a npm package via CI', testcase: params[:testcase] do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding) - package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding) Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb index e913df0957d..1eed68d1b88 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Package' do - describe 'Package Registry', :skip_live_env, :orchestrated, :reliable, :packages, :object_storage, product_group: :package_registry do + describe 'Package Registry', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'npm project level endpoint' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures @@ -81,7 +81,7 @@ module QA it 'push and pull a npm package via CI', testcase: params[:testcase] do Resource::Repository::Commit.fabricate_via_api! do |commit| npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding) - package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package.json.erb')).result(binding) commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' 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 48b9fdec2e9..5413ae85dcd 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, :reliable, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'NuGet group level endpoint' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb index ad5835d8c9d..9a192bc005f 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, -product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'NuGet project level endpoint' do include Support::Helpers::MaskToken diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb index 4e072412bd4..63549113517 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :skip_live_env, :orchestrated, :packages, :object_storage, product_group: :package_registry do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, product_group: :package_registry do describe 'PyPI Repository' do include Runtime::Fixtures include Support::Helpers::MaskToken diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb index 1af1fc7c231..0c58d41d96e 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :object_storage, - feature_flag: { name: 'rubygem_packages', scope: :project } do + RSpec.describe 'Package', :object_storage, except: { job: 'relative-url' }, + feature_flag: { name: 'rubygem_packages', scope: :project } do describe 'RubyGems Repository', product_group: :package_registry do include Runtime::Fixtures diff --git a/qa/qa/specs/helpers/context_selector.rb b/qa/qa/specs/helpers/context_selector.rb index ab8e9bc3639..88b3efb84cb 100644 --- a/qa/qa/specs/helpers/context_selector.rb +++ b/qa/qa/specs/helpers/context_selector.rb @@ -101,7 +101,13 @@ module QA end def pipeline_from_project_name(project_name) - project_name.to_s.start_with?('gitlab-qa') ? Runtime::Env.default_branch : project_name + if project_name.to_s.start_with?('gitlab-qa') + Runtime::Env.default_branch + elsif project_name.to_s == 'gitlab' && Runtime::Env.schedule_type == 'nightly' + 'nightly' + else + project_name + end end # Get production domain value based on GitLab edition and URI's top level domain diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb index aa274a4e101..1601dd46c62 100644 --- a/qa/qa/specs/spec_helper.rb +++ b/qa/qa/specs/spec_helper.rb @@ -12,6 +12,7 @@ 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! +QA::Service::DockerRun::Video.configure! # Enable zero monkey patching mode before loading any other RSpec code. RSpec.configure(&:disable_monkey_patching!) diff --git a/qa/qa/support/helpers/zuora.rb b/qa/qa/support/helpers/zuora.rb new file mode 100644 index 00000000000..0f75d9bb1e6 --- /dev/null +++ b/qa/qa/support/helpers/zuora.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module QA + module Support + module Helpers + module Zuora + ZUORA_TIMEOUT = 60 + end + end + end +end diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb index 27d8b144f3b..d7bf8b76924 100644 --- a/qa/qa/support/knapsack_report.rb +++ b/qa/qa/support/knapsack_report.rb @@ -46,6 +46,28 @@ module QA logger.warn("Falling back to '#{FALLBACK_REPORT}'") end + # Create a copy of the report that contains the selective tests and has '-selective' suffix + # + # @param [String] qa_tests + # @return [void] + def create_for_selective(qa_tests) + timed_specs = JSON.parse(File.read(report_path)) + + qa_tests_array = qa_tests.split(' ') + filtered_timed_specs = timed_specs.select { |k, _| qa_tests_array.any? { |qa_test| k.include? qa_test } } + File.write(selective_path, filtered_timed_specs.to_json) + end + + # Add '-selective-parallel' suffix to report name + # + # @return [String] + def selective_path + extension = File.extname(report_path) + directory = File.dirname(report_path) + file_name = File.basename(report_path, extension) + File.join(directory, "#{file_name}-selective-parallel#{extension}") + end + # Rename and move new regenerated report to a separate folder used to indicate report name # # @return [void] diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb index 2856602629a..ef7875eb838 100644 --- a/qa/qa/support/wait_for_requests.rb +++ b/qa/qa/support/wait_for_requests.rb @@ -39,6 +39,12 @@ module QA Capybara.page.has_no_css?('.gl-spinner', wait: wait) end end + + def wait_for_gitlab_to_respond + Waiter.wait_until(sleep_interval: 5, message: '502 - GitLab is taking too much time to respond') do + Capybara.page.has_no_text?('GitLab is taking too much time to respond') + end + end end end end diff --git a/qa/qa/tools/migrate_influx_data_to_gcs.rb b/qa/qa/tools/migrate_influx_data_to_gcs.rb new file mode 100644 index 00000000000..4933251511b --- /dev/null +++ b/qa/qa/tools/migrate_influx_data_to_gcs.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'csv' +require "fog/google" + +module QA + module Tools + class MigrateInfluxDataToGcs + include Support::InfluxdbTools + + # Google Cloud Storage bucket from which Snowpipe would pull data into Snowflake + QA_GCS_BUCKET_NAME = ENV["QA_GCS_BUCKET_NAME"] || raise("Missing QA_GCS_BUCKET_NAME env variable") + QA_GCS_PROJECT_ID = ENV["QA_GCS_PROJECT_ID"] || raise("Missing QA_GCS_PROJECT_ID env variable") + QA_GCS_JSON_KEY_FILE = ENV["QA_GCS_JSON_KEY_FILE"] || raise("Missing QA_GCS_JSON_KEY_FILE env variable") + INFLUX_STATS_TYPE = %w[test-stats fabrication-stats].freeze + INFLUX_BUCKETS = [Support::InfluxdbTools::INFLUX_TEST_METRICS_BUCKET, + Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET].freeze + TEST_STATS_FIELDS = %w[id testcase file_path name product_group stage job_id job_name + job_url pipeline_id pipeline_url merge_request merge_request_iid smoke reliable quarantined + retried retry_attempts run_time run_type status ui_fabrication api_fabrication total_fabrication].freeze + FABRICATION_STATS_FIELDS = %w[timestamp resource fabrication_method http_method run_type + merge_request fabrication_time info job_url].freeze + + def initialize(range) + @range = range.to_i + end + + # Run Influx Migrator + # @param [Integer] the last x hours for which data is required + # @return [void] + def self.run(range: 1) + migrator = new(range) + + QA::Runtime::Logger.info("Fetching Influx data for the last #{range} hours") + migrator.migrate_data + end + + # Fetch data from Influx DB , store as CSV and upload to GCS + # @return [void] + def migrate_data + INFLUX_BUCKETS.each do |bucket| + INFLUX_STATS_TYPE.each do |stats_type| + if bucket == Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET && stats_type == "fabrication-stats" + break + end + + file_name = "#{bucket.end_with?('main') ? 'main' : 'all'}-#{stats_type}_#{Time.now.to_i}.csv" + influx_to_csv(bucket, stats_type, file_name) + + # Upload to Google Cloud Storage + upload_to_gcs(QA_GCS_BUCKET_NAME, file_name) + end + end + end + + private + + # FluxQL query used to fetch data + # @param [String] influx bucket to fetch data + # @param [String] Type of data to fetch + # @return [String] query string + def query(influx_bucket, stats_type) + <<~QUERY + from(bucket: "#{influx_bucket}") + |> range(start: -#{@range}h) + |> filter(fn: (r) => r["_measurement"] == "#{stats_type}") + |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") + |> drop(columns: ["_start", "_stop", "result", "table", "_time", "_measurement"]) + QUERY + end + + # Query InfluxDB and store in CSV + # @param [String] influx bucket to fetch data + # @param [String] Type of data to fetch + # @param [String] CSV filename to store data + # @return void + def influx_to_csv(influx_bucket, stats_type, data_file_name) + all_runs = query_api.query(query: query(influx_bucket, stats_type)) + CSV.open(data_file_name, "wb") do |csv| + stats_array = stats_type == "test-stats" ? TEST_STATS_FIELDS : FABRICATION_STATS_FIELDS + csv << stats_array.flatten + all_runs.each do |table| + table.records.each do |record| + csv << stats_array.map { |key| record.values[key] } + end + end + QA::Runtime::Logger.info("File #{data_file_name} contains #{all_runs.count} rows") + end + end + + # Fetch GCS Credentials + # @return [Hash] GCS Credentials + def gcs_credentials + json_key = ENV["QA_GCS_JSON_KEY_FILE"] || raise( + "QA_GCS_JSON_KEY_FILE env variable is required!" + ) + return { google_json_key_location: json_key } if File.exist?(json_key) + + { google_json_key_string: json_key } + end + + # Upload file to GCS + # @param [String] bucket to be uploaded to + # @param [String] path of file to be uploaded + # return void + def upload_to_gcs(bucket, backup_file_path) + client = Fog::Storage::Google.new(google_project: QA_GCS_PROJECT_ID, **gcs_credentials) + file_path = backup_file_path.tr('_0-9', '') + + # Backup older file + begin + QA::Runtime::Logger.info("Backing up older file to #{backup_file_path}") + client.copy_object(bucket, file_path, bucket, backup_file_path) + rescue Google::Apis::ClientError + QA::Runtime::Logger.warn("File #{file_path} is not found in GCS bucket, continuing with upload...") + end + + # Upload new file + file = client.put_object(bucket, file_path, File.new(backup_file_path, "r"), force: true) + QA::Runtime::Logger.info("File #{file_path} uploaded to gs://#{bucket}/#{file.name}") + end + end + end +end diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb index 7f4b8f78618..fd76cc3d1a8 100644 --- a/qa/qa/tools/test_resources_handler.rb +++ b/qa/qa/tools/test_resources_handler.rb @@ -93,17 +93,18 @@ module QA # Download files from GCS bucket by environment name # Delete the files afterward def download(ci_project_name) - bucket_items = gcs_storage.list_objects(BUCKET, prefix: ci_project_name).items + logger.info("Downloading resource files from GCS for #{ci_project_name}...") + bucket_items = gcs_storage.list_objects(BUCKET, prefix: "#{ci_project_name}/").items - files_list = bucket_items&.each_with_object([]) do |obj, arr| - arr << obj.name - end - - if files_list.blank? + if bucket_items.blank? logger.info("\nNothing to download!") return end + files_list = bucket_items.each_with_object([]) do |obj, arr| + arr << obj.name + end + FileUtils.mkdir_p('tmp/') files_list.each do |file_name| @@ -217,11 +218,10 @@ module QA def api_client abort("\nPlease provide GITLAB_ADDRESS") unless ENV['GITLAB_ADDRESS'] - abort("\nPlease provide GITLAB_QA_ACCESS_TOKEN") unless ENV['GITLAB_QA_ACCESS_TOKEN'] @api_client ||= Runtime::API::Client.new( ENV['GITLAB_ADDRESS'], - personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'] + personal_access_token: personal_access_token ) end @@ -243,6 +243,17 @@ module QA @json_key ||= ENV["QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS"] end + + # In environments that we can run tests with admin scope, + # we should use GITLAB_QA_ADMIN_ACCESS_TOKEN to clean up resources. + # This is necessary for cleaning up User resources. + def personal_access_token + if ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN'].blank? && ENV['GITLAB_QA_ACCESS_TOKEN'].blank? + abort("\nPlease provide either GITLAB_QA_ADMIN_ACCESS_TOKEN or GITLAB_QA_ACCESS_TOKEN") + end + + @personal_access_token ||= ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN'] || ENV['GITLAB_QA_ACCESS_TOKEN'] + end end end end diff --git a/qa/qa/vendor/jira/jira_api.rb b/qa/qa/vendor/jira/jira_api.rb index 15039ac244e..4248ee589b5 100644 --- a/qa/qa/vendor/jira/jira_api.rb +++ b/qa/qa/vendor/jira/jira_api.rb @@ -21,9 +21,11 @@ module QA end def fetch_issue(issue_key) - response = get("#{api_url}/issue/#{issue_key}", - user: Runtime::Env.jira_admin_username, - password: Runtime::Env.jira_admin_password) + response = get( + "#{api_url}/issue/#{issue_key}", + user: Runtime::Env.jira_admin_username, + password: Runtime::Env.jira_admin_password + ) parse_body(response) end diff --git a/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json b/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json new file mode 100644 index 00000000000..adf506c9d30 --- /dev/null +++ b/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json @@ -0,0 +1,5 @@ +{ + "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 18.85673829499956, + "qa/specs/features/api/3_create/repository/files_spec.rb": 3.180753622999873, + "qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb": 0.010764157999801682 +} diff --git a/qa/spec/fixtures/knapsack_report/instance.json b/qa/spec/fixtures/knapsack_report/instance.json new file mode 100644 index 00000000000..3d659bc53fb --- /dev/null +++ b/qa/spec/fixtures/knapsack_report/instance.json @@ -0,0 +1,7 @@ +{ + "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 18.85673829499956, + "qa/specs/features/api/3_create/repository/files_spec.rb": 3.180753622999873, + "qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb": 0.010764157999801682, + "qa/specs/features/api/9_data_stores/users_spec.rb": 0.2808277129997805, + "qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 4.882168451000325 +} diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index fbf58b5e18a..da1fd224564 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -73,7 +73,7 @@ RSpec.describe QA::Page::Element do subject { described_class.new(:something, /link_to 'something'/) } it 'has an attribute[pattern] of the pattern' do - expect(subject.attributes[:pattern]).to eq /link_to 'something'/ + expect(subject.attributes[:pattern]).to eq(/link_to 'something'/) end it 'is not required by default' do @@ -98,7 +98,7 @@ RSpec.describe QA::Page::Element do subject { described_class.new(:something, /link_to 'something_else_entirely'/, required: true) } it 'has an attribute[pattern] of the passed pattern' do - expect(subject.attributes[:pattern]).to eq /link_to 'something_else_entirely'/ + expect(subject.attributes[:pattern]).to eq(/link_to 'something_else_entirely'/) end it 'is required' do @@ -118,6 +118,10 @@ RSpec.describe QA::Page::Element do expect(subject.selector_css).to include(%q([data-qa-selector="my_element"])) end + it 'properly translates to a data-testid' do + expect(subject.selector_css).to include(%q([data-testid="my_element"])) + end + context 'additional selectors' do let(:element) { described_class.new(:my_element, index: 3, another_match: 'something') } let(:required_element) { described_class.new(:my_element, required: true, index: 3) } diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb index e0bfccf5e78..d7e16c1f84b 100644 --- a/qa/spec/resource/base_spec.rb +++ b/qa/spec/resource/base_spec.rb @@ -362,7 +362,8 @@ RSpec.describe QA::Resource::Base do it 'calls #visit with the underlying #web_url' do allow(resource).to receive(:current_url).and_return(subject.current_url) - expect(wait_for_requests_class).to receive(:wait_for_requests).with({ skip_resp_code_check: false }).twice + expect(wait_for_requests_class).to receive(:wait_for_requests).with({ skip_finished_loading_check: false, + skip_resp_code_check: false }).twice resource.web_url = subject.current_url resource.visit! @@ -372,12 +373,24 @@ RSpec.describe QA::Resource::Base do it 'calls #visit with the underlying #web_url with skip_resp_code_check specified as true' do allow(resource).to receive(:current_url).and_return(subject.current_url) - expect(wait_for_requests_class).to receive(:wait_for_requests).with({ skip_resp_code_check: true }).twice + expect(wait_for_requests_class).to receive(:wait_for_requests).with({ skip_finished_loading_check: false, + skip_resp_code_check: true }).twice resource.web_url = subject.current_url resource.visit!(skip_resp_code_check: true) expect(resource).to have_received(:visit).with(subject.current_url) end + + it 'calls #visit with the underlying #web_url with skip_finished_loading_check specified as true' do + allow(resource).to receive(:current_url).and_return(subject.current_url) + expect(wait_for_requests_class).to receive(:wait_for_requests).with({ skip_finished_loading_check: true, + skip_resp_code_check: false }).twice + + resource.web_url = subject.current_url + resource.visit!(skip_finished_loading_check: true) + + expect(resource).to have_received(:visit).with(subject.current_url) + end end end diff --git a/qa/spec/service/docker_run/video_spec.rb b/qa/spec/service/docker_run/video_spec.rb new file mode 100644 index 00000000000..81be5ccffae --- /dev/null +++ b/qa/spec/service/docker_run/video_spec.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +module QA + RSpec.describe Service::DockerRun::Video do + include QA::Support::Helpers::StubEnv + + let(:rspec_config) { instance_double('RSpec::Core::Configuration', append_after: nil, prepend_before: nil) } + let(:video_recorder_image) { 'presidenten/selenoid-manual-video-recorder' } + let(:video_recorder_version) { 'latest' } + let(:selenoid_browser_image) { 'selenoid/chrome' } + let(:selenoid_browser_version) { '111.0' } + let(:remote_grid) { 'selenoid:4444' } + let(:record_video) { 'true' } + let(:use_selenoid) { 'true' } + let(:docs_link) do + 'https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/running_against_remote_grid.md#testing-with-selenoid' + end + + shared_examples 'video set up fails' do + it 'does not perform configuration' do + aggregate_failures do + expect(QA::Runtime::Logger).to have_received(:warn) + .with(/Test failure video recording setup failed!/) + + expect(RSpec).not_to have_received(:configure) + end + end + end + + shared_examples 'video set up missing variable' do |missing_variable| + let(:failure_message) do + <<~FAIL.tr("\n", ' ').strip + Aborting video recording setup! + Missing variables: #{missing_variable} is required! + See docs: #{docs_link} + FAIL + end + + it 'aborts video setup with warning' do + aggregate_failures do + expect(QA::Runtime::Logger).to have_received(:warn) + .with(failure_message) + + expect(described_class).not_to have_received(:get_container_name) + expect(RSpec).not_to have_received(:configure) + end + end + end + + before do + stub_env('QA_VIDEO_RECORDER_IMAGE', video_recorder_image) + stub_env('QA_VIDEO_RECORDER_VERSION', video_recorder_version) + stub_env('QA_SELENOID_BROWSER_IMAGE', selenoid_browser_image) + stub_env('QA_SELENOID_BROWSER_VERSION', selenoid_browser_version) + stub_env('QA_REMOTE_GRID', remote_grid) + stub_env('USE_SELENOID', use_selenoid) + stub_env('QA_RECORD_VIDEO', record_video) + + allow(RSpec).to receive(:configure).and_yield(rspec_config) + allow(described_class).to receive(:get_container_name) + allow(described_class).to receive(:shell) + allow(QA::Runtime::Logger).to receive(:warn) + allow(QA::Runtime::Logger).to receive(:info) + end + + context 'with video disabled' do + let(:record_video) { 'false' } + + before do + described_class.configure! + end + + it 'skips configuration' do + aggregate_failures do + expect(described_class).not_to have_received(:get_container_name) + expect(described_class).not_to have_received(:shell) + expect(RSpec).not_to have_received(:configure) + end + end + end + + context 'with use_selenoid disabled' do + let(:use_selenoid) { 'false' } + + before do + described_class.configure! + end + + it_behaves_like 'video set up missing variable', 'USE_SELENOID' + end + + context 'without video_recorder_image set' do + let(:video_recorder_image) { nil } + + before do + described_class.configure! + end + + it_behaves_like 'video set up missing variable', 'QA_VIDEO_RECORDER_IMAGE' + end + + context 'without selenoid_browser_image set' do + let(:selenoid_browser_image) { nil } + + before do + described_class.configure! + end + + it_behaves_like 'video set up missing variable', 'QA_SELENOID_BROWSER_IMAGE' + end + + context 'without selenoid_browser_version set' do + let(:selenoid_browser_version) { nil } + + before do + described_class.configure! + end + + it_behaves_like 'video set up missing variable', 'QA_SELENOID_BROWSER_VERSION' + end + + context 'without browser_container_hostname' do + before do + allow(described_class).to receive(:get_container_name) + .with(video_recorder_image) + .and_return('recorder_container_name') + allow(described_class).to receive(:get_container_name) + .with("#{selenoid_browser_image}:#{selenoid_browser_version}") + .and_return('browser_image_hostname') + allow(described_class).to receive(:shell) + .and_return(false) + + described_class.configure! + end + + it_behaves_like 'video set up fails' + end + + context 'without recorder_container_name' do + before do + allow(described_class).to receive(:get_container_name) + .with(video_recorder_image) + .and_return('') + allow(described_class).to receive(:get_container_name) + .with("#{selenoid_browser_image}:#{selenoid_browser_version}") + .and_return('browser_image_hostname') + allow(described_class).to receive(:shell) + .and_return('browser_container_hostname') + + described_class.configure! + end + + it_behaves_like 'video set up fails' + end + + context 'with recorder_container_name and browser_container_hostname' do + before do + allow(described_class).to receive(:get_container_name) + .with(video_recorder_image) + .and_return('recorder_container_name') + allow(described_class).to receive(:get_container_name) + .with("#{selenoid_browser_image}:#{selenoid_browser_version}") + .and_return('browser_image_hostname') + allow(described_class).to receive(:shell) + .and_return('browser_container_hostname') + + described_class.configure! + end + + it 'performs configuration' do + aggregate_failures do + expect(QA::Runtime::Logger).to have_received(:info) + .with(/Test failure video recording setup complete!/) + expect(RSpec).to have_received(:configure) + expect(rspec_config).to have_received(:prepend_before) + expect(rspec_config).to have_received(:append_after) + end + end + end + end +end diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb index f6134cc6177..3550e78d9e3 100644 --- a/qa/spec/specs/helpers/context_selector_spec.rb +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -535,6 +535,47 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do end end end + + context 'with CI_PROJECT_NAME set to gitlab and SCHEDULE_TYPE set to nightly' do + before do + stub_env('CI_PROJECT_NAME', 'gitlab') + stub_env('SCHEDULE_TYPE', 'nightly') + end + + it 'runs on designated pipeline' do + group = describe_successfully do + it('runs on nightly', only: { pipeline: :nightly }) {} + it('does not run in not_nightly', only: { pipeline: :not_nightly }) {} + it('runs on nightly given an array', only: { pipeline: [:canary, :nightly] }) {} + it('does not run in not_nightly given an array', only: { pipeline: [:not_nightly, :canary] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + expect(group.examples[3].execution_result.status).to eq(:pending) + end + end + + context 'when excluding contexts' do + it 'skips designated pipeline' do + group = describe_successfully do + it('skips nightly', except: { pipeline: :nightly }) {} + it('runs in not_nightly', except: { pipeline: :not_nightly }) {} + it('skips on nightly given an array', except: { pipeline: [:canary, :nightly] }) {} + it('runs in not_nightly given an array', except: { pipeline: [:not_nightly, :canary] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + end + end + end end context 'with job constraints' do diff --git a/qa/spec/support/knapsack_report_spec.rb b/qa/spec/support/knapsack_report_spec.rb new file mode 100644 index 00000000000..914a30513e5 --- /dev/null +++ b/qa/spec/support/knapsack_report_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.describe QA::Support::KnapsackReport do + subject(:knapsack_report) { described_class.new('instance') } + + describe '#create_for_selective' do + let(:qa_tests) do + <<~CMD + qa/specs/features/api/3_create + qa/specs/features/browser_ui/3_create/ + qa/specs/features/ee/api/3_create/ + qa/specs/features/ee/browser_ui/3_create/ + CMD + end + + let(:fixtures_path) { 'spec/fixtures/knapsack_report' } + let(:expected_output) { JSON.parse(File.read(File.join(fixtures_path, 'instance-selective-parallel.json'))) } + + before do + allow(File).to receive(:read).and_call_original + allow(File).to receive(:read) + .with('knapsack/instance.json') + .and_return(File.read(File.join(fixtures_path, 'instance.json'))) + end + + it 'creates a filtered file based on qa_tests' do + expect(File).to receive(:write) + .with('knapsack/instance-selective-parallel.json', expected_output.to_json) + + knapsack_report.create_for_selective(qa_tests) + end + end + + describe '#selective_path' do + it 'returns the path with file name suffixed with -selective-parallel' do + expect(knapsack_report.selective_path).to eq('knapsack/instance-selective-parallel.json') + end + end +end diff --git a/qa/spec/support/loglinking_spec.rb b/qa/spec/support/loglinking_spec.rb index 79004630253..2bea528028f 100644 --- a/qa/spec/support/loglinking_spec.rb +++ b/qa/spec/support/loglinking_spec.rb @@ -26,8 +26,11 @@ RSpec.describe QA::Support::Loglinking do context 'and both Sentry and Kibana exist for the logging environment' do let(:sentry) { instance_double(QA::Support::SystemLogs::Sentry, url: sentry_url) } let(:kibana) do - instance_double(QA::Support::SystemLogs::Kibana, - discover_url: discover_url, dashboard_url: dashboard_url) + instance_double( + QA::Support::SystemLogs::Kibana, + discover_url: discover_url, + dashboard_url: dashboard_url + ) end it 'returns both Sentry and Kibana URLs' do @@ -43,8 +46,11 @@ RSpec.describe QA::Support::Loglinking do context 'and only Sentry exists for the logging environment' do let(:sentry) { instance_double(QA::Support::SystemLogs::Sentry, url: sentry_url) } let(:kibana) do - instance_double(QA::Support::SystemLogs::Kibana, - discover_url: nil, dashboard_url: nil) + instance_double( + QA::Support::SystemLogs::Kibana, + discover_url: nil, + dashboard_url: nil + ) end it 'returns only Sentry URL' do @@ -58,8 +64,11 @@ RSpec.describe QA::Support::Loglinking do context 'and only Kibana exists for the logging environment' do let(:sentry) { instance_double(QA::Support::SystemLogs::Sentry, url: nil) } let(:kibana) do - instance_double(QA::Support::SystemLogs::Kibana, - discover_url: discover_url, dashboard_url: dashboard_url) + instance_double( + QA::Support::SystemLogs::Kibana, + discover_url: discover_url, + dashboard_url: dashboard_url + ) end it 'returns only Kibana Discover and Dashboard URLs' do diff --git a/qa/tasks/ci.rake b/qa/tasks/ci.rake index e5f4acb158b..3dfad6a82fd 100644 --- a/qa/tasks/ci.rake +++ b/qa/tasks/ci.rake @@ -30,6 +30,18 @@ namespace :ci do # on run-all label of framework changes do not infer specific tests tests = run_all_label_present || qa_changes.framework_changes? ? nil : qa_changes.qa_tests + # When QA_TESTS only contain folders and no specific specs, populate KNAPSACK_TEST_FILE_PATTERN + if tests && tests.split(' ').none? { |item| item.include?('_spec') } + test_paths = tests.split(' ').map { |item| "#{item}**/*" } + + files_pattern = "{#{test_paths.join(',')}}" + + logger.info(" Files pattern for tests: #{files_pattern}") + append_to_file(env_file, <<~TXT) + KNAPSACK_TEST_FILE_PATTERN='#{files_pattern}' + TXT + end + if run_all_label_present logger.info(" merge request has pipeline:run-all-e2e label, full test suite will be executed") append_to_file(env_file, "QA_RUN_ALL_E2E_LABEL=true\n") diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake index 5e60703ced3..b8a8d6e1145 100644 --- a/qa/tasks/knapsack.rake +++ b/qa/tasks/knapsack.rake @@ -42,6 +42,19 @@ namespace :knapsack do end end + desc "Create knapsack reports from existing reports for selective jobs" + task :create_reports_for_selective do + reports = Dir.glob("knapsack/*").map { |file| file.match(%r{.*/(.*)?\.json})[1] } + + reports.each do |report_name| + unless report_name.include?('-selective-parallel') + QA::Support::KnapsackReport.new(report_name).create_for_selective(ENV['QA_TESTS']) + end + rescue StandardError => e + QA::Runtime::Logger.error(e) + end + end + desc "Merge and upload knapsack report" task :upload, [:glob] do |_task, args| QA::Support::KnapsackReport.configure! diff --git a/qa/tasks/migrate_influx_data_to_gcs.rake b/qa/tasks/migrate_influx_data_to_gcs.rake new file mode 100644 index 00000000000..d6ac6e962f4 --- /dev/null +++ b/qa/tasks/migrate_influx_data_to_gcs.rake @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +desc "Migrate the test results data from InfluxDB to GCS to visualise in Sisense/Tableau" +task :influx_to_gcs, [:range] do |_task, args| + QA::Tools::MigrateInfluxDataToGcs.run(**args) +end diff --git a/qa/tasks/webdrivers.rake b/qa/tasks/webdrivers.rake deleted file mode 100644 index cd2a36ddf6b..00000000000 --- a/qa/tasks/webdrivers.rake +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -load 'webdrivers/Rakefile' |