diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 12:52:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-20 12:52:10 +0000 |
commit | dba864470fbcbb6bdd5b94eb510acdce62c962d8 (patch) | |
tree | e8ead0b84e7b814f5891d2c8cd3db2d6b635fb64 /qa | |
parent | b7d29500f28ff59c8898cdf889a40d3da908f162 (diff) | |
download | gitlab-ce-dba864470fbcbb6bdd5b94eb510acdce62c962d8.tar.gz |
Add latest changes from gitlab-org/gitlab@12-8-stable-ee
Diffstat (limited to 'qa')
91 files changed, 1327 insertions, 459 deletions
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 6d48a9449a5..cd73e1b6539 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -94,7 +94,7 @@ GEM rspec-core (>= 2, < 4, != 2.12.0) ruby-debug-ide (0.7.0) rake (>= 0.8.1) - rubyzip (1.2.2) + rubyzip (1.3.0) selenium-webdriver (3.142.6) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) diff --git a/qa/README.md b/qa/README.md index 1bfa83cadf1..cdc84da1c5e 100644 --- a/qa/README.md +++ b/qa/README.md @@ -67,6 +67,16 @@ bundle exec bin/qa Test::Instance::All http://localhost:3000 Note: If you want to run tests requiring SSH against GDK, you will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md). +#### Running EE tests + +When running EE tests you'll need to have a license available. GitLab engineers can [request a license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee). + +Once you have the license file you can export it as an environment variable and then the framework can use it. If you do so it will be installed automatically. + +``` +export EE_LICENSE=$(cat /path/to/gitlab_license) +``` + ### Writing tests - [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/quick_start_guide.md) @@ -1,6 +1,6 @@ # frozen_string_literal: true -$: << File.expand_path(File.dirname(__FILE__)) +$: << File.expand_path(__dir__) Encoding.default_external = 'UTF-8' @@ -16,6 +16,8 @@ module QA module Flow autoload :Login, 'qa/flow/login' autoload :Project, 'qa/flow/project' + autoload :Saml, 'qa/flow/saml' + autoload :User, 'qa/flow/user' end ## @@ -157,6 +159,10 @@ module QA autoload :Validator, 'qa/page/validator' autoload :Validatable, 'qa/page/validatable' + module SubMenus + autoload :Common, 'qa/page/sub_menus/common' + end + module Main autoload :Login, 'qa/page/main/login' autoload :Menu, 'qa/page/main/menu' @@ -289,6 +295,8 @@ module QA autoload :AddExisting, 'qa/page/project/operations/kubernetes/add_existing' autoload :Show, 'qa/page/project/operations/kubernetes/show' end + + autoload :Metrics, 'qa/page/project/operations/metrics' end module Wiki @@ -429,6 +437,7 @@ module QA autoload :NodeJs, 'qa/service/docker_run/node_js' autoload :GitlabRunner, 'qa/service/docker_run/gitlab_runner' autoload :MailHog, 'qa/service/docker_run/mail_hog' + autoload :SamlIdp, 'qa/service/docker_run/saml_idp' end end @@ -464,6 +473,7 @@ module QA autoload :Configure, 'qa/vendor/jenkins/page/configure' autoload :NewCredentials, 'qa/vendor/jenkins/page/new_credentials' autoload :NewJob, 'qa/vendor/jenkins/page/new_job' + autoload :Job, 'qa/vendor/jenkins/page/job' autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job' end end diff --git a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml new file mode 100644 index 00000000000..a65ae5aa1d9 --- /dev/null +++ b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml @@ -0,0 +1,337 @@ +# This is stripped down version of the .gitlab-ci.yml found +# here: https://gitlab.com/joshlambert/autodevops-deploy. +# +# It performs only the deploy stage. + +image: alpine:latest + +variables: + # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level. + AUTO_DEVOPS_DOMAIN: my-fake-domain.com + + POSTGRES_USER: user + POSTGRES_PASSWORD: testing-password + POSTGRES_ENABLED: 'false' + POSTGRES_DB: $CI_ENVIRONMENT_SLUG + + KUBERNETES_VERSION: 1.11.6 + HELM_VERSION: 2.12.2 + + DOCKER_DRIVER: overlay2 + +stages: + - production + +# This job continuously deploys to production on every push to `master`. + +production: + stage: production + script: + - check_kube_domain + - install_dependencies + - download_chart + - ensure_namespace + - initialize_tiller + - create_secret + - deploy + - persist_environment_url + environment: + name: production + url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + artifacts: + paths: [environment_url.txt] + only: + refs: + - master + kubernetes: active + +# --------------------------------------------------------------------------- + +.auto_devops: &auto_devops | + # Auto DevOps variables and functions + [[ "$TRACE" ]] && set -x + auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB} + export DATABASE_URL=${DATABASE_URL-$auto_database_url} + export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG + export CI_APPLICATION_TAG=$CI_COMMIT_SHA + export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} + export TILLER_NAMESPACE=$KUBE_NAMESPACE + # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products + export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') + + + function get_replicas() { + track="${1:-stable}" + percentage="${2:-100}" + + env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' ) + env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' ) + + if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then + # for stable track get number of replicas from `PRODUCTION_REPLICAS` + eval new_replicas=\$${env_slug}_REPLICAS + if [[ -z "$new_replicas" ]]; then + new_replicas=$REPLICAS + fi + else + # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS` + eval new_replicas=\$${env_track}_${env_slug}_REPLICAS + if [[ -z "$new_replicas" ]]; then + eval new_replicas=\${env_track}_REPLICAS + fi + fi + + replicas="${new_replicas:-1}" + replicas="$(($replicas * $percentage / 100))" + + # always return at least one replicas + if [[ $replicas -gt 0 ]]; then + echo "$replicas" + else + echo 1 + fi + } + + + # Extracts variables prefixed with K8S_SECRET_ + # and creates a Kubernetes secret. + # + # e.g. If we have the following environment variables: + # K8S_SECRET_A=value1 + # K8S_SECRET_B=multi\ word\ value + # + # Then we will create a secret with the following key-value pairs: + # data: + # A: dmFsdWUxCg== + # B: bXVsdGkgd29yZCB2YWx1ZQo= + function create_application_secret() { + track="${1-stable}" + export APPLICATION_SECRET_NAME=$(application_secret_name "$track") + + env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables + + kubectl create secret \ + -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \ + --from-env-file k8s_prefixed_variables -o yaml --dry-run | + kubectl replace -n "$KUBE_NAMESPACE" --force -f - + + export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1) + + rm k8s_prefixed_variables + } + + function deploy_name() { + name="$CI_ENVIRONMENT_SLUG" + track="${1-stable}" + + if [[ "$track" != "stable" ]]; then + name="$name-$track" + fi + + echo $name + } + + function application_secret_name() { + track="${1-stable}" + name=$(deploy_name "$track") + + echo "${name}-secret" + } + + + function deploy() { + track="${1-stable}" + percentage="${2:-100}" + name=$(deploy_name "$track") + + replicas="1" + service_enabled="true" + postgres_enabled="$POSTGRES_ENABLED" + + # if track is different than stable, + # re-use all attached resources + if [[ "$track" != "stable" ]]; then + service_enabled="false" + postgres_enabled="false" + fi + + replicas=$(get_replicas "$track" "$percentage") + + if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then + secret_name='gitlab-registry' + else + secret_name='' + fi + + create_application_secret "$track" + + env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]') + eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS + if [ -n "$env_ADDITIONAL_HOSTS" ]; then + additional_hosts="{$env_ADDITIONAL_HOSTS}" + elif [ -n "$ADDITIONAL_HOSTS" ]; then + additional_hosts="{$ADDITIONAL_HOSTS}" + fi + + if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then + echo "Deploying first release with database initialization..." + helm upgrade --install \ + --wait \ + --set service.enabled="$service_enabled" \ + --set releaseOverride="$CI_ENVIRONMENT_SLUG" \ + --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \ + --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \ + --set image.pullPolicy=IfNotPresent \ + --set image.secrets[0].name="$secret_name" \ + --set application.track="$track" \ + --set application.database_url="$DATABASE_URL" \ + --set application.secretName="$APPLICATION_SECRET_NAME" \ + --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ + --set service.url="$CI_ENVIRONMENT_URL" \ + --set service.additionalHosts="$additional_hosts" \ + --set replicaCount="$replicas" \ + --set postgresql.enabled="$postgres_enabled" \ + --set postgresql.nameOverride="postgres" \ + --set postgresql.postgresUser="$POSTGRES_USER" \ + --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \ + --set postgresql.postgresDatabase="$POSTGRES_DB" \ + --set application.initializeCommand="$DB_INITIALIZE" \ + --set gitlab.app="$CI_PROJECT_PATH_SLUG" \ + --set gitlab.env="$CI_ENVIRONMENT_SLUG" \ + --namespace="$KUBE_NAMESPACE" \ + "$name" \ + chart/ + + echo "Deploying second release..." + helm upgrade --reuse-values \ + --wait \ + --set application.initializeCommand="" \ + --set application.migrateCommand="$DB_MIGRATE" \ + --namespace="$KUBE_NAMESPACE" \ + "$name" \ + chart/ + else + echo "Deploying new release..." + helm upgrade --install \ + --wait \ + --set service.enabled="$service_enabled" \ + --set releaseOverride="$CI_ENVIRONMENT_SLUG" \ + --set image.repository="registry.gitlab.com/joshlambert/ruby-gke/master" \ + --set image.tag="63492726c2264a0277141d6a6573c3d22ecd7de3" \ + --set image.pullPolicy=IfNotPresent \ + --set image.secrets[0].name="$secret_name" \ + --set application.track="$track" \ + --set application.database_url="$DATABASE_URL" \ + --set application.secretName="$APPLICATION_SECRET_NAME" \ + --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ + --set service.url="$CI_ENVIRONMENT_URL" \ + --set service.additionalHosts="$additional_hosts" \ + --set replicaCount="$replicas" \ + --set postgresql.enabled="$postgres_enabled" \ + --set postgresql.nameOverride="postgres" \ + --set postgresql.postgresUser="$POSTGRES_USER" \ + --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \ + --set postgresql.postgresDatabase="$POSTGRES_DB" \ + --set application.migrateCommand="$DB_MIGRATE" \ + --set gitlab.app="$CI_PROJECT_PATH_SLUG" \ + --set gitlab.env="$CI_ENVIRONMENT_SLUG" \ + --namespace="$KUBE_NAMESPACE" \ + "$name" \ + chart/ + fi + + kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/$name" + } + + + function install_dependencies() { + apk add -U openssl curl tar gzip bash ca-certificates git + wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub + wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk + apk add glibc-2.23-r3.apk + rm glibc-2.23-r3.apk + + curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx + mv linux-amd64/helm /usr/bin/ + mv linux-amd64/tiller /usr/bin/ + helm version --client + tiller -version + + curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" + chmod +x /usr/bin/kubectl + kubectl version --client + } + + function download_chart() { + if [[ ! -d chart ]]; then + auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app} + auto_chart_name=$(basename $auto_chart) + auto_chart_name=${auto_chart_name%.tgz} + else + auto_chart="chart" + auto_chart_name="chart" + fi + + helm init --client-only + helm repo add gitlab https://charts.gitlab.io + if [[ ! -d "$auto_chart" ]]; then + helm fetch ${auto_chart} --untar + fi + if [ "$auto_chart_name" != "chart" ]; then + mv ${auto_chart_name} chart + fi + + helm dependency update chart/ + helm dependency build chart/ + } + + function ensure_namespace() { + kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE" + } + + function check_kube_domain() { + if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then + echo "In order to deploy or use Review Apps, AUTO_DEVOPS_DOMAIN variable must be set" + echo "You can do it in Auto DevOps project settings or defining a secret variable at group or project level" + echo "You can also manually add it in .gitlab-ci.yml" + false + else + true + fi + } + + function initialize_tiller() { + echo "Checking Tiller..." + + export HELM_HOST="localhost:44134" + tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 & + echo "Tiller is listening on ${HELM_HOST}" + + if ! helm version --debug; then + echo "Failed to init Tiller." + return 1 + fi + echo "" + } + + function create_secret() { + echo "Create secret..." + if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then + return + fi + + kubectl create secret -n "$KUBE_NAMESPACE" \ + docker-registry gitlab-registry \ + --docker-server="$CI_REGISTRY" \ + --docker-username="$CI_REGISTRY_USER" \ + --docker-password="$CI_REGISTRY_PASSWORD" \ + --docker-email="$GITLAB_USER_EMAIL" \ + -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - + } + + function persist_environment_url() { + echo $CI_ENVIRONMENT_URL > environment_url.txt + } + +before_script: + - *auto_devops diff --git a/qa/qa/flow/saml.rb b/qa/qa/flow/saml.rb new file mode 100644 index 00000000000..676be2beb01 --- /dev/null +++ b/qa/qa/flow/saml.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module QA + module Flow + module Saml + module_function + + def page + Capybara.current_session + end + + def logout_from_idp(saml_idp_service) + Runtime::Logger.debug("Logging out of IDP by visiting \"#{saml_idp_service.idp_sign_out_url}\"") + + Support::Waiter.wait_until(sleep_interval: 1, reload_page: page) do + page.visit saml_idp_service.idp_sign_out_url + page.has_content?("You have been logged out.") + end + end + + def enable_saml_sso(group, saml_idp_service) + page.visit Runtime::Scenario.gitlab_address + + Page::Main::Login.perform(&:sign_in_using_credentials) unless Page::Main::Menu.perform(&:signed_in?) + + visit_saml_sso_settings(group) + + Support::Retrier.retry_on_exception do + EE::Page::Group::Settings::SamlSSO.perform do |saml_sso| + saml_sso.set_id_provider_sso_url(saml_idp_service.idp_sso_url) + saml_sso.set_cert_fingerprint(saml_idp_service.idp_certificate_fingerprint) + saml_sso.click_save_changes + + saml_sso.user_login_url_link_text + end + end + end + + def visit_saml_sso_settings(group, direct: false) + if direct + url = "#{group.web_url}/-/saml" + Runtime::Logger.debug("Visiting url \"#{url}\" directly") + page.visit url + else + group.visit! + + Page::Group::Menu.perform(&:go_to_saml_sso_group_settings) + end + # The toggle buttons take a moment to switch to the correct status. + # I am not sure of a better, less complex way to wait for them to reflect their actual status. + sleep 2 + end + + def run_saml_idp_service(group_name) + service = Service::DockerRun::SamlIdp.new(Runtime::Scenario.gitlab_address, group_name).tap do |runner| + runner.pull + runner.register! + end + + service + end + + def remove_saml_idp_service(saml_idp_service) + saml_idp_service.remove! + end + + def login_to_idp_if_required(username, password) + Vendor::SAMLIdp::Page::Login.perform { |login_page| login_page.login_if_required(username, password) } + end + end + end +end diff --git a/qa/qa/flow/user.rb b/qa/qa/flow/user.rb new file mode 100644 index 00000000000..acc77cb9830 --- /dev/null +++ b/qa/qa/flow/user.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + module Flow + module User + module_function + + def page + Capybara.current_session + end + + def confirm_user(username) + Flow::Login.while_signed_in_as_admin do + Page::Main::Menu.perform(&:go_to_admin_area) + Page::Admin::Menu.perform(&:go_to_users_overview) + Page::Admin::Overview::Users::Index.perform do |index| + index.search_user(username) + index.click_user(username) + end + + Page::Admin::Overview::Users::Show.perform(&:confirm_user) + end + end + end + end +end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index a4c44f78ad4..22b0021ea77 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -26,13 +26,13 @@ module QA wait_for_requests end - def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true, raise_on_failure: false) + def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true, raise_on_failure: true) Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval, raise_on_failure: raise_on_failure) do yield || (reload && refresh && false) end end - def retry_until(max_attempts: 3, reload: false, sleep_interval: 0, raise_on_failure: false) + def retry_until(max_attempts: 3, reload: false, sleep_interval: 0, raise_on_failure: true) Support::Retrier.retry_until(max_attempts: max_attempts, reload_page: (reload && self), sleep_interval: sleep_interval, raise_on_failure: raise_on_failure) do yield end @@ -177,7 +177,7 @@ module QA # The number of selectors should be able to be reduced after # migration to the new spinner is complete. # https://gitlab.com/groups/gitlab-org/-/epics/956 - has_no_css?('.gl-spinner, .fa-spinner, .spinner', wait: Capybara.default_max_wait_time) + has_no_css?('.gl-spinner, .fa-spinner, .spinner', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) end def finished_loading_block? @@ -255,12 +255,6 @@ module QA click_link text end - def click_body - wait_for_requests - - find('body').click - end - def visit_link_in_element(name) visit find_element(name)['href'] end diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb index d3e44fd867d..ef9bfa560ce 100644 --- a/qa/qa/page/component/ci_badge_link.rb +++ b/qa/qa/page/component/ci_badge_link.rb @@ -5,7 +5,22 @@ module QA module Component module CiBadgeLink COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running - PASSED_STATUS = 'passed'.freeze + INCOMPLETE_STATUSES = %w[pending created running].freeze + + # e.g. def passed?(timeout: nil); status_badge == 'passed'; end + COMPLETED_STATUSES.map do |status| + define_method "#{status}?" do |timeout: nil| + timeout ? completed?(timeout: timeout) : completed? + status_badge == status + end + end + + # e.g. def pending?; status_badge == 'pending'; end + INCOMPLETE_STATUSES.map do |status| + define_method "#{status}?" do + status_badge == status + end + end def self.included(base) base.view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do @@ -17,12 +32,6 @@ module QA find_element(:status_badge).text end - def successful?(timeout: 60) - raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout) - - status_badge == PASSED_STATUS - end - private def completed?(timeout: 60) diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index 1dd718a1d88..e667fad1dd3 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -34,6 +34,10 @@ module QA has_css?('.select2-active', wait: 1) has_no_css?('.select2-active', wait: 30) end + + def dropdown_open? + has_css?('.select2-input') + end end end end diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb index 4a8e65e20af..df4c2902b31 100644 --- a/qa/qa/page/dashboard/snippet/new.rb +++ b/qa/qa/page/dashboard/snippet/new.rb @@ -10,17 +10,25 @@ module QA end view 'app/views/shared/snippets/_form.html.haml' do + element :description_field + element :description_placeholder element :snippet_title element :snippet_file_name element :create_snippet_button end + view 'app/views/projects/_zen.html.haml' do + # This 'element' is here only to ensure the changes in the view source aren't mistakenly changed + element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern + end + def fill_title(title) fill_element :snippet_title, title end def fill_description(description) - fill_element :issuable_form_description, description + click_element :description_placeholder + fill_element :description_field, description end def set_visibility(visibility) diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index 2b3b872aff4..230511ce6f6 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -13,15 +13,22 @@ module QA element :contribution_analytics_link end + view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do + element :analytics_link + element :analytics_sidebar_submenu + end + def click_group_members_item within_sidebar do click_element(:group_members_item) end end - def click_group_analytics_item - within_sidebar do - click_element(:contribution_analytics_link) + def click_contribution_analytics_item + hover_element(:analytics_link) do + within_submenu(:analytics_sidebar_submenu) do + click_element(:contribution_analytics_link) + end end end diff --git a/qa/qa/page/group/sub_menus/common.rb b/qa/qa/page/group/sub_menus/common.rb index a378db80e4b..96efc8da98d 100644 --- a/qa/qa/page/group/sub_menus/common.rb +++ b/qa/qa/page/group/sub_menus/common.rb @@ -5,6 +5,8 @@ module QA module Group module SubMenus module Common + include QA::Page::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_group.html.haml' do @@ -13,23 +15,10 @@ module QA end end - def hover_element(element) - within_sidebar do - find_element(element).hover - yield - end - end + private - def within_sidebar - within_element(:group_sidebar) do - yield - end - end - - def within_submenu(element) - within_element(element) do - yield - end + def sidebar_element + :group_sidebar end end end diff --git a/qa/qa/page/layout/performance_bar.rb b/qa/qa/page/layout/performance_bar.rb index 4e144e67f12..c593783a730 100644 --- a/qa/qa/page/layout/performance_bar.rb +++ b/qa/qa/page/layout/performance_bar.rb @@ -21,9 +21,9 @@ module QA has_element?(:performance_bar) end - def has_detailed_metrics?(count) + def has_detailed_metrics?(minimum_count) retry_until(sleep_interval: 1) do - all_elements(:detailed_metric_content, count: count).all? do |metric| + all_elements(:detailed_metric_content, minimum: minimum_count).all? do |metric| metric.has_text?(%r{\d+}) end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index ad5b3c97cb9..b69990bae9c 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -55,7 +55,7 @@ module QA element :diffs_tab end - view 'app/assets/javascripts/diffs/components/diff_line_gutter_content.vue' do + view 'app/assets/javascripts/diffs/components/diff_table_cell.vue' do element :diff_comment end @@ -155,6 +155,8 @@ module QA def merge! click_element :merge_button if ready_to_merge? + finished_loading? + raise "Merge did not appear to be successful" unless merged? end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index a1e1bb4bc98..a56083ea25c 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -124,7 +124,7 @@ module QA end end - click_body + click_element(:edit_link_labels) labels.each do |label| has_element?(:labels_block, text: label) diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 07dea3449f1..26db2f20c1b 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -13,11 +13,15 @@ module QA::Page element :pipeline_path end + view 'app/assets/javascripts/jobs/components/sidebar.vue' do + element :retry_button + end + def successful?(timeout: 60) raise "Timed out waiting for the build trace to load" unless loaded? raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout) - status_badge == PASSED_STATUS + passed? end # Reminder: You may wish to wait for a particular job status before checking output @@ -33,6 +37,10 @@ module QA::Page result end + def retry! + click_element :retry_button + end + private def loaded?(wait: 60) diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index d0e8011d82d..97214e22820 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -32,8 +32,7 @@ module QA def choose_namespace(namespace) retry_on_exception do - click_body - click_element :project_namespace_select + click_element :project_namespace_select unless dropdown_open? search_and_select(namespace) end end diff --git a/qa/qa/page/project/operations/metrics.rb b/qa/qa/page/project/operations/metrics.rb new file mode 100644 index 00000000000..418cc925186 --- /dev/null +++ b/qa/qa/page/project/operations/metrics.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Operations + class Metrics < Page::Base + EXPECTED_TITLE = 'Memory Usage (Total)' + LOADING_MESSAGE = 'Waiting for performance data' + + view 'app/assets/javascripts/monitoring/components/dashboard.vue' do + element :prometheus_graphs + end + + view 'app/assets/javascripts/monitoring/components/charts/time_series.vue' do + element :prometheus_graph_widgets + end + + view 'app/assets/javascripts/monitoring/components/panel_type.vue' do + element :prometheus_widgets_dropdown + element :alert_widget_menu_item + end + + def wait_for_metrics + wait_for_data + return if has_metrics? + + wait_until(max_duration: 180) do + wait_for_data + has_metrics? + end + end + + def wait_for_data + wait_until(reload: false) { !has_text?(LOADING_MESSAGE) } if has_text?(LOADING_MESSAGE) + end + + def has_metrics? + within_element :prometheus_graphs do + has_text?(EXPECTED_TITLE) + end + end + end + end + end + end +end + +QA::Page::Project::Operations::Metrics.prepend_if_ee('QA::EE::Page::Project::Operations::Metrics') diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 684ad4a59d5..f018e4bf018 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -9,6 +9,7 @@ module QA::Page view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do element :pipeline_commit_status + element :pipeline_retry_button end def click_on_latest_pipeline @@ -18,10 +19,25 @@ module QA::Page end def wait_for_latest_pipeline_success + wait_for_latest_pipeline_status { has_text?('passed') } + end + + def wait_for_latest_pipeline_completion + wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') } + end + + def wait_for_latest_pipeline_status wait_until(reload: false, max_duration: 300) do - within_element_by_index(:pipeline_commit_status, 0) do - has_text?('passed') - end + within_element_by_index(:pipeline_commit_status, 0) { yield } + end + end + + def wait_for_latest_pipeline_success_or_retry + wait_for_latest_pipeline_completion + + if has_text?('failed') + click_element :pipeline_retry_button + wait_for_latest_pipeline_success end end end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 45fffbf6000..1003b828a32 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -30,9 +30,9 @@ module QA::Page element :pipeline_badges end - def running? + def running?(wait: 0) within('.ci-header-container') do - page.has_content?('running') + page.has_content?('running', wait: wait) end end diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb index 64a182e5b3a..2bb285d6086 100644 --- a/qa/qa/page/project/settings/ci_variables.rb +++ b/qa/qa/page/project/settings/ci_variables.rb @@ -52,6 +52,14 @@ module QA end end + def remove_variable(location: :first) + within('.ci-variable-row-body', match: location) do + find('button.ci-variable-row-remove-button').click + end + + save_variables + end + private def toggle_masked(masked_node, masked) diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index f718311fbf2..9d302acb058 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -47,6 +47,7 @@ module QA def protect_branch click_element(:protect_button, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) + wait_for_requests end private diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index c619bd6d6a3..61047c42fcc 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -7,6 +7,14 @@ module QA include Page::Component::ClonePanel include Page::Project::SubMenus::Settings + view 'app/assets/javascripts/repository/components/table/row.vue' do + element :file_name_link + end + + view 'app/assets/javascripts/repository/components/table/index.vue' do + element :file_tree_table + end + view 'app/views/layouts/header/_new_dropdown.haml' do element :new_menu_toggle element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern @@ -17,7 +25,8 @@ module QA end view 'app/views/projects/_home_panel.html.haml' do - element :project_name + element :forked_from_link + element :project_name_content end view 'app/views/projects/_files.html.haml' do @@ -37,10 +46,6 @@ module QA element :quick_actions end - view 'app/views/projects/tree/_tree_content.html.haml' do - element :file_tree - end - view 'app/views/projects/tree/_tree_header.html.haml' do element :add_to_tree element :new_file_option @@ -79,14 +84,18 @@ module QA click_on 'Fork' end + def forked_from?(parent_project_name) + has_element?(:forked_from_link, text: parent_project_name) + end + def click_file(filename) - within_element(:file_tree) do + within_element(:file_tree_table) do click_on filename end end def click_commit(commit_msg) - within_element(:file_tree) do + within_element(:file_tree_table) do click_on commit_msg end end @@ -96,6 +105,16 @@ module QA click_link 'New issue' end + def has_file?(name) + within_element(:file_tree_table) do + has_element?(:file_name_link, text: name) + end + end + + def has_name?(name) + has_element?(:project_name_content, text: name) + end + def last_commit_content find_element(:commit_content).text end @@ -113,7 +132,7 @@ module QA end def project_name - find('.qa-project-name').text + find_element(:project_name_content).text end def switch_to_branch(branch_name) diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb index 3c9e8085748..da759398cff 100644 --- a/qa/qa/page/project/sub_menus/common.rb +++ b/qa/qa/page/project/sub_menus/common.rb @@ -5,20 +5,12 @@ module QA module Project module SubMenus module Common - def within_sidebar - within('.sidebar-top-level-items') do - yield - end - end + include QA::Page::SubMenus::Common + + private - def within_submenu - if has_css?('.fly-out-list') - within('.fly-out-list') do - yield - end - else - yield - end + def sidebar_element + :project_sidebar end end end diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb index d266cb21417..bcbc1dc16d3 100644 --- a/qa/qa/page/project/sub_menus/operations.rb +++ b/qa/qa/page/project/sub_menus/operations.rb @@ -12,6 +12,7 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :link_operations element :operations_environments_link + element :operations_metrics_link end end end @@ -24,6 +25,14 @@ module QA end end + def go_to_operations_metrics + hover_operations do + within_submenu do + click_element(:operations_metrics_link) + end + end + end + def go_to_operations_kubernetes hover_operations do within_submenu do diff --git a/qa/qa/page/sub_menus/common.rb b/qa/qa/page/sub_menus/common.rb new file mode 100644 index 00000000000..0eb1b100bd7 --- /dev/null +++ b/qa/qa/page/sub_menus/common.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module QA + module Page + module SubMenus + module Common + def hover_element(element) + within_sidebar do + find_element(element).hover + yield + end + end + + def within_sidebar + within_element(sidebar_element) do + yield + end + end + + def within_submenu(element = nil) + if element + within_element(element) do + yield + end + else + within_submenu_without_element do + yield + end + end + end + + private + + def within_submenu_without_element + if has_css?('.fly-out-list') + within('.fly-out-list') do + yield + end + else + yield + end + end + + def sidebar_element + raise NotImplementedError + end + end + end + end +end diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index e6057433b55..cac58c599ea 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -8,10 +8,12 @@ module QA module ApiFabricator include Capybara::DSL - ResourceNotFoundError = Class.new(RuntimeError) ResourceFabricationFailedError = Class.new(RuntimeError) - ResourceURLMissingError = Class.new(RuntimeError) ResourceNotDeletedError = Class.new(RuntimeError) + ResourceNotFoundError = Class.new(RuntimeError) + ResourceQueryError = Class.new(RuntimeError) + ResourceUpdateFailedError = Class.new(RuntimeError) + ResourceURLMissingError = Class.new(RuntimeError) attr_reader :api_resource, :api_response attr_writer :api_client @@ -31,6 +33,12 @@ module QA resource_web_url(api_post) end + def reload! + api_get + + self + end + def remove_via_api! api_delete end diff --git a/qa/qa/resource/events/base.rb b/qa/qa/resource/events/base.rb index f98a54a6f57..91ec0e59e37 100644 --- a/qa/qa/resource/events/base.rb +++ b/qa/qa/resource/events/base.rb @@ -3,7 +3,7 @@ module QA module Resource module Events - MAX_WAIT = 10 + MAX_WAIT = 60 RAISE_ON_FAILURE = true EventNotFoundError = Class.new(RuntimeError) diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb index 73f1b0b9695..854dd92e89c 100644 --- a/qa/qa/resource/fork.rb +++ b/qa/qa/resource/fork.rb @@ -3,19 +3,24 @@ module QA module Resource class Fork < Base + attribute :name do + upstream.name + end + attribute :project do - Resource::Project.fabricate! do |resource| - resource.name = upstream.project.name - resource.path_with_namespace = "#{user.name}/#{upstream.project.name}" + Resource::Project.fabricate_via_api! do |resource| + resource.add_name_uuid = false + resource.name = name + resource.path_with_namespace = "#{user.username}/#{name}" end end attribute :upstream do - Repository::ProjectPush.fabricate! + Repository::ProjectPush.fabricate!.project end attribute :user do - User.fabricate! do |resource| + User.fabricate_via_api! do |resource| if Runtime::Env.forker? resource.username = Runtime::Env.forker_username resource.password = Runtime::Env.forker_password @@ -33,7 +38,7 @@ module QA login.sign_in_using_credentials(user: user) end - upstream.project.visit! + upstream.visit! Page::Project::Show.perform(&:fork_project) @@ -47,6 +52,41 @@ module QA populate(:project) end + + def fabricate_via_api! + populate(:upstream, :user) + + Runtime::Logger.debug("Forking project #{upstream.name} to namespace #{user.username}...") + super + wait_until_forked + + populate(:project) + end + + def api_get_path + "/projects/#{CGI.escape(path_with_namespace)}" + end + + def api_post_path + "/projects/#{upstream.id}/fork" + end + + def api_post_body + { + namespace: user.username, + name: name, + path: name + } + end + + def wait_until_forked + Runtime::Logger.debug("Waiting for the fork process to complete...") + forked = wait_until do + project.import_status == "finished" + end + + raise "Timed out while waiting for the fork process to complete." unless forked + end end end end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 0824512d238..a30bb8cbc77 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -16,6 +16,7 @@ module QA attribute :id attribute :name + attribute :runners_token def initialize @path = Runtime::Namespace.name diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index c738a91a77f..38a620a5427 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -11,6 +11,10 @@ module QA post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level } end + def remove_member(user) + delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url + end + def list_members JSON.parse(get(Runtime::API::Request.new(api_client, api_members_path).url).body) end diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb index 9cb4e6a49ca..d9c86b3b527 100644 --- a/qa/qa/resource/merge_request_from_fork.rb +++ b/qa/qa/resource/merge_request_from_fork.rb @@ -8,7 +8,7 @@ module QA attr_accessor :fork_branch attribute :fork do - Fork.fabricate! + Fork.fabricate_via_browser_ui! end attribute :push do diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index f2ca0e0b8fd..62e55e18e9b 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -94,6 +94,10 @@ module QA "#{api_get_path}/runners" end + def api_put_path + "/projects/#{id}" + end + def api_post_path '/projects' end @@ -115,6 +119,35 @@ module QA post_body end + def change_repository_storage(new_storage) + put_body = { repository_storage: new_storage } + response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body + + unless response.code == HTTP_STATUS_OK + raise ResourceUpdateFailedError, "Could not change repository storage to #{new_storage}. Request returned (#{response.code}): `#{response}`." + end + + wait_until do + reload! + + api_response[:repository_storage] == new_storage + end + end + + def import_status + response = get Runtime::API::Request.new(api_client, "/projects/#{id}/import").url + + unless response.code == HTTP_STATUS_OK + raise ResourceQueryError, "Could not get import status. Request returned (#{response.code}): `#{response}`." + end + + result = parse_body(response) + + Runtime::Logger.error("Import failed: #{result[:import_error]}") if result[:import_status] == "failed" + + result[:import_status] + end + def runners(tag_list: nil) response = get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url parse_body(response) diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index 11aad4ae65f..4d6b37937b4 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -3,33 +3,17 @@ module QA module Resource class ProjectMilestone < Base - attr_reader :title - attr_accessor :description + attribute :id + attribute :title attribute :project do - Project.fabricate! - end - - def title=(title) - @title = "#{title}-#{SecureRandom.hex(4)}" - @description = 'A milestone' - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform do |menu| - menu.click_issues - menu.click_milestones + Project.fabricate_via_api! do |resource| + resource.name = 'project-with-milestone' end + end - Page::Project::Milestone::Index.perform(&:click_new_milestone) - - Page::Project::Milestone::New.perform do |milestone_new| - milestone_new.set_title(@title) - milestone_new.set_description(@description) - milestone_new.click_milestone_create_button - end + def initialize + @title = "project-milestone-#{SecureRandom.hex(4)}" end def api_get_path @@ -42,8 +26,7 @@ module QA def api_post_body { - description: @description, - title: @title + title: title } end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 54c13071cef..7b427af6b74 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -63,6 +63,10 @@ module QA '/groups' end + def api_delete_path + "/groups/#{id}" + end + def api_post_body { path: path, diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 9544a3e80b3..e6dbe3faa61 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -35,14 +35,17 @@ module QA end def email - @email ||= "#{username}@example.com" + @email ||= begin + api_email = api_resource&.dig(:email) + api_email && !api_email.empty? ? api_email : "#{username}@example.com" + end end def public_email @public_email ||= begin api_public_email = api_resource&.dig(:public_email) - api_public_email && api_public_email != '' ? api_public_email : Runtime::User.default_email + api_public_email && !api_public_email.empty? ? api_public_email : Runtime::User.default_email end end @@ -87,6 +90,8 @@ module QA end def api_get_path + return "/user" if fetching_own_data? + "/users/#{fetch_id(username)}" end @@ -136,6 +141,10 @@ module QA users.first[:id] end + + def fetching_own_data? + user&.username == username || Runtime::User.username == username + end end end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 340f6dc0356..69ba90702be 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -15,10 +15,6 @@ module QA CAPYBARA_MAX_WAIT_TIME = 10 - class << self - attr_accessor :rspec_configured, :capybara_configured - end - def initialize self.class.configure! end @@ -49,40 +45,11 @@ module QA end def self.configure! - configure_rspec! - configure_capybara! - end - - def self.configure_rspec! - # We don't want to enter this infinite loop: - # Runtime::Release.perform_before_hooks -> `QA::Runtime::Browser.visit` -> configure! -> configure_rspec! -> Runtime::Release.perform_before_hooks - # So we make sure this method is called only once. - return if self.rspec_configured - - browser = self - RSpec.configure do |config| config.define_derived_metadata(file_path: %r{/qa/specs/features/}) do |metadata| metadata[:type] = :feature end - - config.before do - unless browser.rspec_configured - browser.rspec_configured = true - - ## - # Perform before hooks, which are different for CE and EE - # - Runtime::Release.perform_before_hooks - end - end end - end - - def self.configure_capybara! - return if self.capybara_configured - - self.capybara_configured = true Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 6514e41e279..1c947b0329f 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -22,6 +22,10 @@ module QA SUPPORTED_FEATURES end + def additional_repository_storage + ENV['QA_ADDITIONAL_REPOSITORY_STORAGE'] + end + def admin_password ENV['GITLAB_ADMIN_PASSWORD'] end diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index 25fc02a887e..5e948b5b850 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -38,6 +38,8 @@ module QA end raise SetFeatureError, "#{key} was not enabled!" unless is_enabled + + QA::Runtime::Logger.info("Successfully enabled and verified feature flag: #{key}") end end @@ -46,6 +48,12 @@ module QA feature && feature["state"] == "on" end + def get_features + request = Runtime::API::Request.new(api_client, "/features") + response = get(request.url) + response.body + end + private def api_client @@ -74,12 +82,6 @@ module QA raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`." end end - - def get_features - request = Runtime::API::Request.new(api_client, "/features") - response = get(request.url) - response.body - end end end end diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb index 18a6736afcf..1cd0c62563b 100644 --- a/qa/qa/runtime/release.rb +++ b/qa/qa/runtime/release.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'capybara/rspec' +require 'capybara-screenshot/rspec' + module QA module Runtime ## @@ -24,6 +27,13 @@ module QA def self.method_missing(name, *args) self.new.strategy.public_send(name, *args) + rescue + saved = Capybara::Screenshot.screenshot_and_save_page + + QA::Runtime::Logger.error("Screenshot: #{saved[:image]}") if saved&.key?(:image) + QA::Runtime::Logger.error("HTML capture: #{saved[:html]}") if saved&.key?(:html) + + raise end end end diff --git a/qa/qa/runtime/search.rb b/qa/qa/runtime/search.rb index 74402301098..29a71b2815c 100644 --- a/qa/qa/runtime/search.rb +++ b/qa/qa/runtime/search.rb @@ -35,6 +35,8 @@ module QA ) end + verify_search_engine_ok(search_term) + find_commit(commit, "commit*#{search_term}") find_project(project, "to-search*#{search_term}") end @@ -74,9 +76,7 @@ module QA end def search(scope, term) - QA::Runtime::Logger.debug("Search scope '#{scope}' for '#{term}'...") - request = Runtime::API::Request.new(api_client, "/search?scope=#{scope}&search=#{term}") - response = get(request.url) + response = get_response(scope, term) unless response.code == singleton_class::HTTP_STATUS_OK msg = "Search attempt failed. Request returned (#{response.code}): `#{response}`." @@ -87,6 +87,19 @@ module QA parse_body(response) end + def get_response(scope, term) + QA::Runtime::Logger.debug("Search scope '#{scope}' for '#{term}'...") + request = Runtime::API::Request.new(api_client, "/search?scope=#{scope}&search=#{term}") + get(request.url) + end + + def verify_search_engine_ok(search_term) + response = get_response('commits', search_term) + if response.code.to_s =~ /5[0-9][0-9]/ + raise ElasticSearchServerError, "elasticsearch attempt returned code #{response.code}. Check that search was conducted on the appropriate url and port." + end + end + def api_client @api_client ||= Runtime::API::Client.new(:gitlab) end diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb index 97373f7a059..74d4c8f8757 100644 --- a/qa/qa/scenario/template.rb +++ b/qa/qa/scenario/template.rb @@ -23,6 +23,11 @@ module QA def perform(options, *args) extract_address(:gitlab_address, options, args) + ## + # Perform before hooks, which are different for CE and EE + # + Runtime::Release.perform_before_hooks + Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature) Specs::Runner.perform do |specs| diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb index 79dad7f4619..b4098619e4e 100644 --- a/qa/qa/scenario/test/instance.rb +++ b/qa/qa/scenario/test/instance.rb @@ -20,6 +20,11 @@ module QA def self.do_perform(address, *rspec_options) Runtime::Scenario.define(:gitlab_address, address) + ## + # Perform before hooks, which are different for CE and EE + # + Runtime::Release.perform_before_hooks + Specs::Runner.perform do |specs| specs.tty = true specs.options = rspec_options if rspec_options.any? diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index 99497cbe0ad..b2c90ab2e2a 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -34,8 +34,9 @@ module QA Please update the code in `qa/` directory to make it match current changes in this merge request. - For more help see documentation in `qa/page/README.md` file or - ask for help on #quality channel on Slack (GitLab Team only). + For more help see documentation in + https://docs.gitlab.com/ee/development/testing_guide/end_to_end/page_objects.html + or ask for help on #quality channel on Slack (GitLab Team only). If you are not a Team Member, and you still need help to contribute, please open an issue in GitLab QA issue tracker. diff --git a/qa/qa/service/docker_run/saml_idp.rb b/qa/qa/service/docker_run/saml_idp.rb new file mode 100644 index 00000000000..a0638bbcc2e --- /dev/null +++ b/qa/qa/service/docker_run/saml_idp.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class SamlIdp < Base + def initialize(gitlab_host, group) + @image = 'jamedjo/test-saml-idp' + @name = 'saml-idp-server' + @gitlab_host = gitlab_host + @group = group + super() + end + + def idp_base_url + "https://#{host_name}:8443/simplesaml" + end + + def idp_sso_url + "#{idp_base_url}/saml2/idp/SSOService.php" + end + + def idp_sign_out_url + "#{idp_base_url}/module.php/core/authenticate.php?as=example-userpass&logout" + end + + def idp_signed_out_url + "#{idp_base_url}/logout.php" + end + + def idp_metadata_url + "#{idp_base_url}/saml2/idp/metadata.php" + end + + def idp_issuer + idp_metadata_url + end + + def idp_certificate_fingerprint + QA::Runtime::Env.simple_saml_fingerprint || '119b9e027959cdb7c662cfd075d9e2ef384e445f' + end + + def host_name + return 'localhost' unless QA::Runtime::Env.running_in_ci? + + super + end + + def register! + command = <<~CMD.tr("\n", ' ') + docker run -d --rm + --network #{network} + --hostname #{host_name} + --name #{@name} + --env SIMPLESAMLPHP_SP_ENTITY_ID=#{@gitlab_host}/groups/#{@group} + --env SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=#{@gitlab_host}/groups/#{@group}/-/saml/callback + --publish 8080:8080 + --publish 8443:8443 + #{@image} + CMD + + command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci? + + shell command + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb index 2543c0091fb..f14fcc5afce 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb +++ b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb @@ -2,35 +2,34 @@ module QA context 'Plan' do - describe 'Close issue' do + include Support::Api + + describe 'Issue' do let(:issue) do Resource::Issue.fabricate_via_api! end let(:issue_id) { issue.api_response[:iid] } - before do - Flow::Login.sign_in + let(:api_client) { Runtime::API::Client.new(:gitlab) } + before do # Initial commit should be pushed because # the very first commit to the project doesn't close the issue # https://gitlab.com/gitlab-org/gitlab-foss/issues/38965 push_commit('Initial commit') end - it 'user closes an issue by pushing commit' do + it 'closes via pushing a commit' do push_commit("Closes ##{issue_id}", false) - issue.visit! - - Page::Project::Issue::Show.perform do |show| - reopen_issue_button_visible = show.wait_until(reload: true) do - show.has_element?(:reopen_issue_button, wait: 1.0) - end - expect(reopen_issue_button_visible).to be_truthy + Support::Retrier.retry_until(max_duration: 10, sleep_interval: 1) do + issue_closed? end end + private + def push_commit(commit_message, new_branch = true) Resource::Repository::ProjectPush.fabricate! do |push| push.commit_message = commit_message @@ -39,6 +38,11 @@ module QA push.project = issue.project end end + + def issue_closed? + response = get Runtime::API::Request.new(api_client, "/projects/#{issue.project.id}/issues/#{issue_id}").url + parse_body(response)[:state] == 'closed' + end end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb index 70303a30153..dea85f68be2 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true module QA - context 'Manage', :orchestrated, :oauth, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196517' do + # This test is skipped instead of quarantine because continuously running + # this test may cause the user to hit GitHub's rate limits thus blocking the user. + # Related issue: https://gitlab.com/gitlab-org/gitlab/issues/196517 + context 'Manage', :orchestrated, :oauth, :skip do describe 'OAuth login' do it 'User logs in to GitLab with GitHub OAuth' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index 9a273e9cd1c..e30afbf8ae0 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -8,10 +8,9 @@ module QA user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) - project = Resource::Project.fabricate_via_api! do |resource| - resource.name = 'add-member-project' - end - project.visit! + Resource::Project.fabricate_via_api! do |project| + project.name = 'add-member-project' + end.visit! Page::Project::Menu.perform(&:go_to_members_settings) Page::Project::Settings::Members.perform do |members| diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 14eaf770f10..409d67d51b1 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module QA - # https://gitlab.com/gitlab-org/gitlab/issues/26952 - # BUG_IN_CODE - context 'Manage', :github, :quarantine do + context 'Manage', :github, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/26952', type: :bug } do describe 'Project import from GitHub' do let(:imported_project) do Resource::ProjectImportedFromGithub.fabricate! do |project| diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb index eecf485a518..5f7a6981f23 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb @@ -8,8 +8,8 @@ module QA end let(:project) do - Resource::Project.fabricate_via_api! do |resource| - resource.name = 'email-notification-test' + Resource::Project.fabricate_via_api! do |project| + project.name = 'email-notification-test' end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb index aa88937504e..57d2c02a27b 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module QA - context 'Plan' do + context 'Plan', :reliable do describe 'check xss occurence in @mentions in issues', :requires_admin do - it 'user mentions a user in comment' do + it 'mentions a user in a comment' do QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token unless QA::Runtime::Env.personal_access_token @@ -21,8 +21,8 @@ module QA Flow::Login.sign_in - project = Resource::Project.fabricate_via_api! do |resource| - resource.name = 'xss-test-for-mentions-project' + project = Resource::Project.fabricate_via_api! do |project| + project.name = 'xss-test-for-mentions-project' end Flow::Project.add_member(project: project, username: user.username) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index e505c0991a6..33d2c7026b3 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan' do + context 'Plan', :reliable do describe 'collapse comments in issue discussions' do let(:my_first_reply) { 'My first reply' } @@ -17,7 +17,7 @@ module QA end end - it 'user collapses and expands reply for comments in an issue' do + it 'collapses and expands reply for comments in an issue' do Page::Project::Issue::Show.perform do |show| one_reply = "1 reply" diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb index 6c37e3ecbb9..4667eb6c587 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan' do + context 'Plan', :reliable do describe 'Issue comments' do before do Flow::Login.sign_in @@ -9,7 +9,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user comments on an issue and edits the comment' do + it 'comments on an issue and edits the comment' do Page::Project::Issue::Show.perform do |show| first_version_of_comment = 'First version of the comment' second_version_of_comment = 'Second version of the comment' diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 3b231b9930e..7b4418191a3 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -7,7 +7,7 @@ module QA Flow::Login.sign_in end - it 'user creates an issue' do + it 'creates an issue', :reliable do issue = Resource::Issue.fabricate_via_browser_ui! Page::Project::Menu.perform(&:click_issues) @@ -27,7 +27,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user comments on an issue with an attachment' do + it 'comments on an issue with an attachment' do Page::Project::Issue::Show.perform do |show| show.comment('See attached banana for scale', attachment: file_to_attach) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index 4156ba54785..b7687f785a8 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan' do + context 'Plan', :reliable do describe 'filter issue comments activities' do before do Flow::Login.sign_in @@ -9,7 +9,7 @@ module QA Resource::Issue.fabricate_via_api!.visit! end - it 'user filters comments and activities in an issue' do + it 'filters comments and activities in an issue' do Page::Project::Issue::Show.perform do |show| my_own_comment = "My own comment" made_the_issue_confidential = "made the issue confidential" diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb index 9a1d13cf677..9b46a066c8e 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan' do + context 'Plan', :reliable do describe 'issue suggestions' do let(:issue_title) { 'Issue Lists are awesome' } @@ -13,7 +13,7 @@ module QA end.project.visit! end - it 'user sees issue suggestions when creating a new issue' do + it 'shows issue suggestions when creating a new issue' do Page::Project::Show.perform(&:go_to_new_issue) Page::Project::Issue::New.perform do |new_page| new_page.add_title("issue") 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 a0647df4097..3e575517ecb 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 @@ -1,16 +1,16 @@ # frozen_string_literal: true module QA - context 'Plan', :smoke do + context 'Plan', :smoke, :reliable do describe 'mention' do before do Flow::Login.sign_in @user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) - project = Resource::Project.fabricate_via_api! do |resource| - resource.name = 'project-to-test-mention' - resource.visibility = 'private' + project = Resource::Project.fabricate_via_api! do |project| + project.name = 'project-to-test-mention' + project.visibility = 'private' end project.add_member(@user) @@ -20,7 +20,7 @@ module QA end.visit! end - it 'user mentions another user in an issue' do + it 'mentions another user in an issue' 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/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index eaa1a2a0da8..4a9901f2a84 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -14,7 +14,7 @@ module QA @merge_request_description = '... to find them, to bring them all, and in the darkness bind them' end - it 'creates a basic merge request', :smoke do + it 'creates a basic merge request' do Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| merge_request.project = @project merge_request.title = @merge_request_title @@ -32,7 +32,6 @@ module QA milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone| milestone.project = @project - milestone.title = 'milestone' end label = Resource::Label.fabricate_via_api! do |label| diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 370bf30f3a4..7d4e6b7efbc 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -6,7 +6,7 @@ module QA it 'user forks a project, submits a merge request and maintainer merges it' do Flow::Login.sign_in - merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request| + merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request| merge_request.fork_branch = 'feature-branch' end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index e4c79bf75b5..6ebe3e0b620 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/36817 - # BUG_IN_CODE - context 'Create', :quarantine do + context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/36817', type: :bug } do describe 'Merge request rebasing' do it 'user rebases source branch of merge request' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb new file mode 100644 index 00000000000..13fe8918f97 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin, quarantine: { type: :new } do + let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } + let(:parent_project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'parent-project' + project.initialize_with_readme = true + end + end + let(:fork_project) do + Resource::Fork.fabricate_via_api! do |fork| + fork.user = user + fork.upstream = parent_project + end.project + end + + before do + parent_project.add_member(user) + end + + it 'creates a 2nd fork after moving the parent project' do + Flow::Login.sign_in(as: user) + + fork_project.visit! + + parent_project.change_repository_storage(QA::Runtime::Env.additional_repository_storage) + + second_fork_project = Resource::Fork.fabricate_via_api! do |fork| + fork.name = "second-fork" + fork.user = user + fork.upstream = parent_project + end.project + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = second_fork_project + push.file_name = 'new_file' + push.file_content = '# This is a new file' + push.commit_message = 'Add new file' + push.new_branch = false + end.project.visit! + + Page::Project::Show.perform do |show| + expect(show).to have_file('new_file') + expect(show).to have_name(second_fork_project.name) + expect(show).to be_forked_from(parent_project.name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb index 1f156dfe6d5..ff0f212c289 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb @@ -2,9 +2,7 @@ module QA # Git protocol v2 is temporarily disabled - # https://gitlab.com/gitlab-org/gitlab/issues/27828 - # BUG_IN_CODE - context 'Create', :quarantine do + context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/27828', type: :bug } do describe 'Push over HTTP using Git protocol version 2', :requires_git_protocol_v2 do it 'user pushes to the repository' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb index 55c70656462..0e8b8203c34 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb @@ -2,9 +2,7 @@ module QA # Git protocol v2 is temporarily disabled - # https://gitlab.com/gitlab-org/gitlab/issues/27828 - # BUG_IN_CODE - context 'Create', :quarantine do + context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/27828', type: :bug } do describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do # Note: If you run this test against GDK make sure you've enabled sshd and # enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 70b571a316a..7c9db5ee496 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create', quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196034' do + context 'Create' do describe 'Web IDE file templates' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb deleted file mode 100644 index c813484347e..00000000000 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module QA - context 'Verify' do - describe 'CI variable support' do - it 'user adds a CI variable', :smoke do - Flow::Login.sign_in - - project = Resource::Project.fabricate_via_api! do |project| - project.name = 'project-with-ci-variables' - project.description = 'project with CI variables' - end - - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'VARIABLE_KEY' - resource.value = 'some_CI_variable' - resource.masked = false - end - - project.visit! - - Page::Project::Menu.perform(&:go_to_ci_cd_settings) - - Page::Project::Settings::CICD.perform do |settings| - settings.expand_ci_variables do |page| - expect(page).to have_field(with: 'VARIABLE_KEY') - expect(page).not_to have_field(with: 'some_CI_variable') - - page.reveal_variables - - expect(page).to have_field(with: 'some_CI_variable') - end - end - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb new file mode 100644 index 00000000000..9dad5ad8fb5 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_remove_ci_variable_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module QA + context 'Verify' do + describe 'Add or Remove CI variable via UI', :smoke do + let!(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-ci-variables' + project.description = 'project with CI variables' + end + end + + before do + Flow::Login.sign_in + add_ci_variable + open_ci_cd_settings + end + + it 'user adds a CI variable' do + Page::Project::Settings::CICD.perform do |settings| + settings.expand_ci_variables do |page| + expect(page).to have_field(with: 'VARIABLE_KEY') + expect(page).not_to have_field(with: 'some_CI_variable') + + page.reveal_variables + + expect(page).to have_field(with: 'some_CI_variable') + end + end + end + + it 'user removes a CI variable' do + Page::Project::Settings::CICD.perform do |settings| + settings.expand_ci_variables do |page| + page.remove_variable + + expect(page).not_to have_field(with: 'VARIABLE_KEY') + end + end + end + + private + + def add_ci_variable + Resource::CiVariable.fabricate_via_browser_ui! do |ci_variable| + ci_variable.project = project + ci_variable.key = 'VARIABLE_KEY' + ci_variable.value = 'some_CI_variable' + ci_variable.masked = false + end + end + + def open_ci_cd_settings + project.visit! + Page::Project::Menu.perform(&:go_to_ci_cd_settings) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index d4853a7bcf3..05adcc718e8 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -1,82 +1,87 @@ # frozen_string_literal: true module QA - context 'Verify', :orchestrated, :docker do + context 'Verify', :docker do describe 'Pipeline creation and processing' do let(:executor) { "qa-runner-#{Time.now.to_i}" } + let(:max_wait) { 30 } - after do - Service::DockerRun::GitlabRunner.new(executor).remove! - end - - it 'users creates a pipeline which gets processed' do - Flow::Login.sign_in - - project = Resource::Project.fabricate! do |project| - project.name = 'project-with-pipelines' - project.description = 'Project with CI/CD Pipelines.' + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-pipeline' end + end + before do Resource::Runner.fabricate! do |runner| runner.project = project runner.name = executor - runner.tags = %w[qa test] + runner.tags = [executor] end + end - Resource::Repository::ProjectPush.fabricate! do |push| - push.project = project - push.file_name = '.gitlab-ci.yml' - push.commit_message = 'Add .gitlab-ci.yml' - push.file_content = <<~EOF - test-success: - tags: - - qa - - test - script: echo 'OK' - - test-failure: - tags: - - qa - - test - script: - - echo 'FAILURE' - - exit 1 - - test-tags: - tags: - - qa - - docker - script: echo 'NOOP' + after do + Service::DockerRun::GitlabRunner.new(executor).remove! + end - test-artifacts: - tags: - - qa - - test - script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt - artifacts: - paths: - - my-artifacts/ - EOF - end.project.visit! + it 'users creates a pipeline which gets processed', :smoke do + Flow::Login.sign_in - expect(page).to have_content('Add .gitlab-ci.yml') + 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 + test-success: + tags: + - #{executor} + script: echo 'OK' - Page::Project::Menu.perform(&:click_ci_cd_pipelines) + test-failure: + tags: + - #{executor} + script: + - echo 'FAILURE' + - exit 1 - expect(page).to have_content('All 1') - expect(page).to have_content('Add .gitlab-ci.yml') + test-tags: + tags: + - invalid + script: echo 'NOOP' - puts 'Waiting for the runner to process the pipeline' - sleep 15 # Runner should process all jobs within 15 seconds. + test-artifacts: + tags: + - #{executor} + script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt + artifacts: + paths: + - my-artifacts/ + YAML + } + ] + ) + end.project.visit! + Page::Project::Menu.perform(&:click_ci_cd_pipelines) Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) - Page::Project::Pipeline::Show.perform do |pipeline| - expect(pipeline).to be_running - expect(pipeline).to have_build('test-success', status: :success) - expect(pipeline).to have_build('test-failure', status: :failed) - expect(pipeline).to have_build('test-tags', status: :pending) - expect(pipeline).to have_build('test-artifacts', status: :success) + { + 'test-success': :passed, + 'test-failure': :failed, + 'test-tags': :pending, + 'test-artifacts': :passed + }.each do |job, status| + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job(job) + end + + Page::Project::Job::Show.perform do |show| + expect(show).to public_send("be_#{status}") + show.click_element(:pipeline_path, Page::Project::Pipeline::Show) + end end end end diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 0ca49bd080b..581e6b8299e 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -3,15 +3,15 @@ require 'digest/sha1' module QA - context 'Release', :docker, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/196047' do + context 'Release', :docker do describe 'Git clone using a deploy key' do before do Flow::Login.sign_in @runner_name = "qa-runner-#{Time.now.to_i}" - @project = Resource::Project.fabricate_via_api! do |resource| - resource.name = 'deploy-key-clone-project' + @project = Resource::Project.fabricate_via_api! do |project| + project.name = 'deploy-key-clone-project' end @repository_location = @project.repository_ssh_location @@ -46,12 +46,7 @@ module QA deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}" - Resource::CiVariable.fabricate_via_browser_ui! do |resource| - resource.project = @project - resource.key = deploy_key_name - resource.value = key.private_key - resource.masked = false - end + make_ci_variable(deploy_key_name, key) gitlab_ci = <<~YAML cat-config: @@ -90,6 +85,17 @@ module QA expect(job.output).to include(sha1sum) end end + + private + + def make_ci_variable(key_name, key) + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = @project + resource.key = key_name + resource.value = key.private_key + resource.masked = false + end + end end end end diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 54014ff7067..0a52b01af03 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -4,94 +4,49 @@ require 'pathname' module QA context 'Configure' do - def disable_optional_jobs(project) - # Disable code_quality check in Auto DevOps pipeline as it takes - # too long and times out the test - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'CODE_QUALITY_DISABLED' - resource.value = '1' - resource.masked = false - end - - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'LICENSE_MANAGEMENT_DISABLED' - resource.value = '1' - resource.masked = false - end - - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'SAST_DISABLED' - resource.value = '1' - resource.masked = false - end - - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'DEPENDENCY_SCANNING_DISABLED' - resource.value = '1' - resource.masked = false - end - - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'CONTAINER_SCANNING_DISABLED' - resource.value = '1' - resource.masked = false + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = Runtime::Env.auto_devops_project_name || 'autodevops-project' + project.auto_devops_enabled = true end + end - Resource::CiVariable.fabricate_via_api! do |resource| - resource.project = project - resource.key = 'DAST_DISABLED' - resource.value = '1' - resource.masked = false - end + before do + disable_optional_jobs(project) end - # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/118481 - describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do + describe 'Auto DevOps support', :orchestrated, :kubernetes do context 'when rbac is enabled' do - before(:all) do - @cluster = Service::KubernetesCluster.new.create! - end + let(:cluster) { Service::KubernetesCluster.new.create! } - after(:all) do - @cluster&.remove! + after do + cluster&.remove! end it 'runs auto devops' do Flow::Login.sign_in - @project = Resource::Project.fabricate! do |p| - p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops' - p.description = 'Project with Auto DevOps' - end - - disable_optional_jobs(@project) - # Set an application secret CI variable (prefixed with K8S_SECRET_) Resource::CiVariable.fabricate! do |resource| - resource.project = @project + resource.project = project resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE' resource.value = 'you_can_see_this_variable' resource.masked = false end # Connect K8s cluster - Resource::KubernetesCluster.fabricate! do |cluster| - cluster.project = @project - cluster.cluster = @cluster - cluster.install_helm_tiller = true - cluster.install_ingress = true - cluster.install_prometheus = true - cluster.install_runner = true + Resource::KubernetesCluster.fabricate! do |k8s_cluster| + k8s_cluster.project = project + k8s_cluster.cluster = cluster + k8s_cluster.install_helm_tiller = true + k8s_cluster.install_ingress = true + k8s_cluster.install_prometheus = true + k8s_cluster.install_runner = true end # Create Auto DevOps compatible repo Resource::Repository::ProjectPush.fabricate! do |push| - push.project = @project + push.project = project push.directory = Pathname .new(__dir__) .join('../../../../../fixtures/auto_devops_rack') @@ -146,20 +101,15 @@ module QA before do Flow::Login.sign_in - @project = Resource::Project.fabricate_via_browser_ui! do |p| - p.name = "project-with-autodevops-#{SecureRandom.hex(8)}" - p.description = 'Project with AutoDevOps' - end + project.visit! Page::Project::Menu.perform(&:go_to_ci_cd_settings) Page::Project::Settings::CICD.perform(&:expand_auto_devops) Page::Project::Settings::AutoDevops.perform(&:enable_autodevops) - @project.visit! - # Create AutoDevOps repo Resource::Repository::ProjectPush.fabricate! do |push| - push.project = @project + push.project = project push.directory = Pathname .new(__dir__) .join('../../../../../fixtures/auto_devops_rack') @@ -176,5 +126,22 @@ module QA end end end + + private + + def disable_optional_jobs(project) + %w[ + CODE_QUALITY_DISABLED LICENSE_MANAGEMENT_DISABLED + SAST_DISABLED DAST_DISABLED DEPENDENCY_SCANNING_DISABLED + CONTAINER_SCANNING_DISABLED + ].each do |key| + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = key + resource.value = '1' + resource.masked = false + end + end + end end end diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb index 4a5bb077e69..c6d5fba919b 100644 --- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb @@ -4,8 +4,8 @@ module QA context 'Non-devops' do describe 'Performance bar display', :requires_admin do context 'when logged in as an admin user' do - # 4 metrics: pg, gitaly, redis, total - let(:metrics_count) { 4 } + # performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided) + let(:minimum_metrics_count) { 3 } before do Flow::Login.sign_in_as_admin @@ -28,7 +28,7 @@ module QA Page::Layout::PerformanceBar.perform do |bar_component| expect(bar_component).to have_performance_bar - expect(bar_component).to have_detailed_metrics(metrics_count) + expect(bar_component).to have_detailed_metrics(minimum_metrics_count) expect(bar_component).to have_request_for('realtime_changes') # Always requested on issue pages end end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index f59795e17c3..281e1b85cc3 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -16,13 +16,6 @@ module QA super end - def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true, raise_on_failure: false) - log("next wait uses reload: #{reload}") - # Logging of wait start/end/duration is handled by QA::Support::Waiter - - super - end - def scroll_to(selector, text: nil) msg = "scrolling to :#{selector}" msg += " with text: #{text}" if text diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb index 53d72f2f410..14771243beb 100644 --- a/qa/qa/support/repeater.rb +++ b/qa/qa/support/repeater.rb @@ -10,13 +10,13 @@ module QA RetriesExceededError = Class.new(RuntimeError) WaitExceededError = Class.new(RuntimeError) - def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false) + def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true) attempts = 0 start = Time.now begin while remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration) - QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts + QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts && log result = yield return result if result diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb index 7b548e95453..f28534e7c11 100644 --- a/qa/qa/support/retrier.rb +++ b/qa/qa/support/retrier.rb @@ -34,7 +34,7 @@ module QA result end - def retry_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: false, retry_on_exception: false) + def retry_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false) # For backwards-compatibility max_attempts = 3 if max_attempts.nil? && max_duration.nil? diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb index 5d5ba70a0c2..c58882a11ea 100644 --- a/qa/qa/support/wait_for_requests.rb +++ b/qa/qa/support/wait_for_requests.rb @@ -6,7 +6,7 @@ module QA module_function def wait_for_requests - Waiter.wait_until do + Waiter.wait_until(log: false) do finished_all_ajax_requests? && finished_all_axios_requests? end end diff --git a/qa/qa/support/waiter.rb b/qa/qa/support/waiter.rb index fe63c930c7c..9ccc0d9484f 100644 --- a/qa/qa/support/waiter.rb +++ b/qa/qa/support/waiter.rb @@ -7,15 +7,17 @@ module QA module_function - def wait_until(max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME, reload_page: nil, sleep_interval: 0.1, raise_on_failure: false, retry_on_exception: false) - QA::Runtime::Logger.debug( - <<~MSG.tr("\n", ' ') - with wait_until: max_duration: #{max_duration}; - reload_page: #{reload_page}; - sleep_interval: #{sleep_interval}; - raise_on_failure: #{raise_on_failure} - MSG - ) + def wait_until(max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME, reload_page: nil, sleep_interval: 0.1, raise_on_failure: true, retry_on_exception: false, log: true) + if log + QA::Runtime::Logger.debug( + <<~MSG.tr("\n", ' ') + with wait_until: max_duration: #{max_duration}; + reload_page: #{reload_page}; + sleep_interval: #{sleep_interval}; + raise_on_failure: #{raise_on_failure} + MSG + ) + end result = nil self.repeat_until( @@ -23,11 +25,12 @@ module QA reload_page: reload_page, sleep_interval: sleep_interval, raise_on_failure: raise_on_failure, - retry_on_exception: retry_on_exception + retry_on_exception: retry_on_exception, + log: log ) do result = yield end - QA::Runtime::Logger.debug("ended wait_until") + QA::Runtime::Logger.debug("ended wait_until") if log result end diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb index 3f752adbe6f..a05ec735632 100644 --- a/qa/qa/tools/delete_subgroups.rb +++ b/qa/qa/tools/delete_subgroups.rb @@ -26,30 +26,19 @@ module QA group_id = fetch_group_id sub_groups_head_response = head Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", per_page: "100").url - total_sub_groups = sub_groups_head_response.headers[:x_total] total_sub_group_pages = sub_groups_head_response.headers[:x_total_pages] - STDOUT.puts "total_sub_groups: #{total_sub_groups}" - STDOUT.puts "total_sub_group_pages: #{total_sub_group_pages}" + sub_group_ids = fetch_subgroup_ids(group_id, total_sub_group_pages) + STDOUT.puts "Number of Sub Groups not already marked for deletion: #{sub_group_ids.length}" - total_sub_group_pages.to_i.times do |page_no| - # Fetch all subgroups for the top level group - sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", per_page: "100").url - - sub_group_ids = JSON.parse(sub_groups_response.body).map { |subgroup| subgroup["id"] } - - if sub_group_ids.any? - STDOUT.puts "\n==== Current Page: #{page_no + 1} ====\n" - - delete_subgroups(sub_group_ids) - end - end + delete_subgroups(sub_group_ids) unless sub_group_ids.empty? STDOUT.puts "\nDone" end private def delete_subgroups(sub_group_ids) + STDOUT.puts "Deleting #{sub_group_ids.length} subgroups..." sub_group_ids.each do |subgroup_id| delete_response = delete Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF\e[0m" @@ -61,6 +50,17 @@ module QA group_search_response = get Runtime::API::Request.new(@api_client, "/groups", search: ENV['GROUP_NAME_OR_PATH'] || 'gitlab-qa-sandbox-group').url JSON.parse(group_search_response.body).first["id"] end + + def fetch_subgroup_ids(group_id, group_pages) + sub_groups_ids = [] + + group_pages.to_i.times do |page_no| + sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", page: (page_no + 1).to_s, per_page: "100").url + sub_groups_ids.concat(JSON.parse(sub_groups_response.body).reject { |subgroup| !subgroup["marked_for_deletion_on"].nil? }.map { |subgroup| subgroup["id"] }) + end + + sub_groups_ids.uniq + end end end end diff --git a/qa/qa/vendor/jenkins/page/job.rb b/qa/qa/vendor/jenkins/page/job.rb new file mode 100644 index 00000000000..498ce6041b8 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/job.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class Job < Page::Base + attr_accessor :job_name + + def path + "/job/#{@job_name}" + end + + def has_successful_build? + page.has_text?("Last successful build") + end + end + end + end + end +end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index a6b61e9b1ee..f2ce9fb2cf8 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -27,24 +27,6 @@ describe QA::Support::Page::Logging do .to output(%r{refreshing http://current-url}).to_stdout_from_any_process end - it 'logs wait' do - expect { subject.wait_until(max_duration: 0) {} } - .to output(/next wait uses reload: true/).to_stdout_from_any_process - expect { subject.wait_until(max_duration: 0) {} } - .to output(/with wait_until/).to_stdout_from_any_process - expect { subject.wait_until(max_duration: 0) {} } - .to output(/ended wait_until$/).to_stdout_from_any_process - end - - it 'logs wait with reload false' do - expect { subject.wait_until(max_duration: 0, reload: false) {} } - .to output(/next wait uses reload: false/).to_stdout_from_any_process - expect { subject.wait_until(max_duration: 0, reload: false) {} } - .to output(/with wait_until/).to_stdout_from_any_process - expect { subject.wait_until(max_duration: 0, reload: false) {} } - .to output(/ended wait_until$/).to_stdout_from_any_process - end - it 'logs scroll_to' do expect { subject.scroll_to(:element) } .to output(/scrolling to :element/).to_stdout_from_any_process diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb index a5ed4422f6e..eb2bdd1be64 100644 --- a/qa/spec/resource/api_fabricator_spec.rb +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -120,7 +120,7 @@ describe QA::Resource::ApiFabricator do end end - context '#transform_api_resource' do + describe '#transform_api_resource' do let(:resource) do Class.new do def self.name diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb index 6112ba7c694..b2d577bd552 100644 --- a/qa/spec/scenario/test/integration/github_spec.rb +++ b/qa/spec/scenario/test/integration/github_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Github do - context '#perform' do + describe '#perform' do let(:env) { spy('Runtime::Env') } before do diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb index cb8a6a630cc..15f15b2e643 100644 --- a/qa/spec/scenario/test/integration/instance_saml_spec.rb +++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::InstanceSAML do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:instance_saml] } end diff --git a/qa/spec/scenario/test/integration/kubernetes_spec.rb b/qa/spec/scenario/test/integration/kubernetes_spec.rb index cb43994b229..51ee7b9acff 100644 --- a/qa/spec/scenario/test/integration/kubernetes_spec.rb +++ b/qa/spec/scenario/test/integration/kubernetes_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Kubernetes do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:kubernetes] } end diff --git a/qa/spec/scenario/test/integration/ldap_spec.rb b/qa/spec/scenario/test/integration/ldap_spec.rb index 86747cd8eb7..c493cde6c7a 100644 --- a/qa/spec/scenario/test/integration/ldap_spec.rb +++ b/qa/spec/scenario/test/integration/ldap_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::LDAPNoTLS do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_tls] } end @@ -9,7 +9,7 @@ describe QA::Scenario::Test::Integration::LDAPNoTLS do end describe QA::Scenario::Test::Integration::LDAPNoServer do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_no_server] } end @@ -17,7 +17,7 @@ describe QA::Scenario::Test::Integration::LDAPNoServer do end describe QA::Scenario::Test::Integration::LDAPTLS do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:ldap_tls] } end diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb index 4e75e72f4d2..4452e890ebe 100644 --- a/qa/spec/scenario/test/integration/mattermost_spec.rb +++ b/qa/spec/scenario/test/integration/mattermost_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::Mattermost do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:args) { %w[gitlab_address mattermost_address] } let(:args) do diff --git a/qa/spec/scenario/test/integration/oauth_spec.rb b/qa/spec/scenario/test/integration/oauth_spec.rb index c1c320be576..ab7ea905a29 100644 --- a/qa/spec/scenario/test/integration/oauth_spec.rb +++ b/qa/spec/scenario/test/integration/oauth_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::OAuth do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:oauth] } end diff --git a/qa/spec/scenario/test/integration/object_storage_spec.rb b/qa/spec/scenario/test/integration/object_storage_spec.rb index 2b7188223e0..8b4367bee32 100644 --- a/qa/spec/scenario/test/integration/object_storage_spec.rb +++ b/qa/spec/scenario/test/integration/object_storage_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe QA::Scenario::Test::Integration::ObjectStorage do - context '#perform' do + describe '#perform' do it_behaves_like 'a QA scenario class' do let(:tags) { [:object_storage] } end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 3d98f03a982..1a3efe0b46c 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -11,7 +11,7 @@ describe QA::Specs::Runner do end end - context '#perform' do + describe '#perform' do before do allow(QA::Runtime::Browser).to receive(:configure!) end diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb index 20dca6608f6..b5d5058ef49 100644 --- a/qa/spec/support/repeater_spec.rb +++ b/qa/spec/support/repeater_spec.rb @@ -381,5 +381,35 @@ describe QA::Support::Repeater do end end end + + it 'logs attempts' do + attempted = false + + expect do + subject.repeat_until(max_attempts: 1) do + unless attempted + attempted = true + break false + end + + true + end + end.to output(/Attempt number/).to_stdout_from_any_process + end + + it 'allows logging to be silenced' do + attempted = false + + expect do + subject.repeat_until(max_attempts: 1, log: false) do + unless attempted + attempted = true + break false + end + + true + end + end.not_to output.to_stdout_from_any_process + end end end diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb index fbe66a680f9..ef1d53e6b65 100644 --- a/qa/spec/support/retrier_spec.rb +++ b/qa/spec/support/retrier_spec.rb @@ -14,12 +14,12 @@ describe QA::Support::Retrier do context 'when the condition is true' do it 'logs max attempts (3 by default)' do expect { subject.retry_until { true } } - .to output(/with retry_until: max_attempts: 3; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process + .to output(/with retry_until: max_attempts: 3; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process end it 'logs max duration' do expect { subject.retry_until(max_duration: 1) { true } } - .to output(/with retry_until: max_duration: 1; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process + .to output(/with retry_until: max_duration: 1; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process end it 'logs the end' do @@ -30,12 +30,12 @@ describe QA::Support::Retrier do context 'when the condition is false' do it 'logs the start' do - expect { subject.retry_until(max_duration: 0) { false } } + expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } } .to output(/with retry_until: max_duration: 0; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process end it 'logs the end' do - expect { subject.retry_until(max_duration: 0) { false } } + expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } } .to output(/ended retry_until$/).to_stdout_from_any_process end end @@ -54,8 +54,8 @@ describe QA::Support::Retrier do subject.retry_until end - it 'sets raise_on_failure to false by default' do - expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: false)) + it 'sets raise_on_failure to true by default' do + expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: true)) subject.retry_until end diff --git a/qa/spec/support/shared_examples/scenario_shared_examples.rb b/qa/spec/support/shared_examples/scenario_shared_examples.rb index 17469ea470c..6e20adbd4ad 100644 --- a/qa/spec/support/shared_examples/scenario_shared_examples.rb +++ b/qa/spec/support/shared_examples/scenario_shared_examples.rb @@ -31,6 +31,12 @@ shared_examples 'a QA scenario class' do expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once) end + it 'performs before hooks only once' do + subject.perform(args) + + expect(release).to have_received(:perform_before_hooks).once + end + it 'sets tags on runner' do subject.perform(args) diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb index 06e404c862a..35f1e01289a 100644 --- a/qa/spec/support/waiter_spec.rb +++ b/qa/spec/support/waiter_spec.rb @@ -34,6 +34,11 @@ describe QA::Support::Waiter do end end + it 'allows logs to be silenced' do + expect { subject.wait_until(max_duration: 0, raise_on_failure: false, log: false) { false } } + .not_to output.to_stdout_from_any_process + end + it 'sets max_duration to 60 by default' do expect(subject).to receive(:repeat_until).with(hash_including(max_duration: 60)) @@ -46,8 +51,8 @@ describe QA::Support::Waiter do subject.wait_until end - it 'sets raise_on_failure to false by default' do - expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: false)) + it 'sets raise_on_failure to true by default' do + expect(subject).to receive(:repeat_until).with(hash_including(raise_on_failure: true)) subject.wait_until end |