diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /qa | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) | |
download | gitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'qa')
168 files changed, 2740 insertions, 965 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile index 7f90e4bf5bf..6310e4b290d 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -3,11 +3,10 @@ LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" ENV DEBIAN_FRONTEND="noninteractive" ENV DOCKER_VERSION="17.09.0-ce" -ENV CHROME_VERSION="83.0.4103.61-1" -ENV CHROME_DRIVER_VERSION="83.0.4103.39" +ENV CHROME_VERSION="84.0.4147.89-1" +ENV CHROME_DRIVER_VERSION="84.0.4147.30" ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb" ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}" -ENV K3D_VERSION="1.3.4" ## # Add support for stretch-backports @@ -49,12 +48,6 @@ RUN unzip chromedriver_linux64.zip -d /usr/local/bin RUN rm -f chromedriver_linux64.zip ## -# Install K3d local cluster support -# https://github.com/rancher/k3d -# -RUN curl -s https://raw.githubusercontent.com/rancher/k3d/master/install.sh | TAG="v${K3D_VERSION}" bash - -## # Install gcloud and kubectl CLI used in Auto DevOps test to create K8s # clusters # diff --git a/qa/Gemfile b/qa/Gemfile index d5c682ef76f..e2951db534a 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -15,6 +15,7 @@ gem 'rspec_junit_formatter', '~> 0.4.1' gem 'faker', '~> 1.6', '>= 1.6.6' gem 'knapsack', '~> 1.17' gem 'parallel_tests', '~> 2.29' +gem 'rotp', '~> 3.1.0' group :test do gem 'pry-byebug', '~> 3.5.1', platform: :mri diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 23324fccdec..c2b876e3b04 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -78,6 +78,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rotp (3.1.0) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -129,6 +130,7 @@ DEPENDENCIES pry-byebug (~> 3.5.1) rake (~> 12.3.0) rest-client (~> 2.1.0) + rotp (~> 3.1.0) rspec (~> 3.7) rspec-retry (~> 0.6.1) rspec_junit_formatter (~> 0.4.1) diff --git a/qa/bin/rubymine b/qa/bin/rubymine new file mode 100755 index 00000000000..0be0cf0ec33 --- /dev/null +++ b/qa/bin/rubymine @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require_relative '../qa' + +ARGV.unshift('Test::Instance::All', ENV['GITLAB_URL'], '--') + +QA::Scenario + .const_get(ARGV.shift) + .launch!(ARGV) @@ -79,6 +79,7 @@ module QA autoload :PersonalAccessToken, 'qa/resource/personal_access_token' autoload :User, 'qa/resource/user' autoload :ProjectMilestone, 'qa/resource/project_milestone' + autoload :GroupMilestone, 'qa/resource/group_milestone' autoload :Members, 'qa/resource/members' autoload :File, 'qa/resource/file' autoload :Fork, 'qa/resource/fork' @@ -181,6 +182,7 @@ module QA autoload :Login, 'qa/page/main/login' autoload :Menu, 'qa/page/main/menu' autoload :OAuth, 'qa/page/main/oauth' + autoload :TwoFactorAuth, 'qa/page/main/two_factor_auth' autoload :SignUp, 'qa/page/main/sign_up' autoload :Terms, 'qa/page/main/terms' end @@ -207,6 +209,11 @@ module QA autoload :Show, 'qa/page/group/show' autoload :Menu, 'qa/page/group/menu' + module Milestone + autoload :Index, 'qa/page/group/milestone/index' + autoload :New, 'qa/page/group/milestone/new' + end + module SubMenus autoload :Common, 'qa/page/group/sub_menus/common' autoload :Members, 'qa/page/group/sub_menus/members' @@ -217,6 +224,12 @@ module QA end end + module Milestone + autoload :Index, 'qa/page/milestone/index' + autoload :New, 'qa/page/milestone/new' + autoload :Show, 'qa/page/milestone/show' + end + module File autoload :Form, 'qa/page/file/form' autoload :Show, 'qa/page/file/show' @@ -254,6 +267,12 @@ module QA autoload :Show, 'qa/page/project/pipeline/show' end + module Tag + autoload :Index, 'qa/page/project/tag/index' + autoload :New, 'qa/page/project/tag/new' + autoload :Show, 'qa/page/project/tag/show' + end + module Job autoload :Show, 'qa/page/project/job/show' end @@ -273,6 +292,7 @@ module QA autoload :Runners, 'qa/page/project/settings/runners' autoload :MergeRequest, 'qa/page/project/settings/merge_request' autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories' + autoload :ProtectedTags, 'qa/page/project/settings/protected_tags' autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions' module Services @@ -301,6 +321,7 @@ module QA autoload :New, 'qa/page/project/issue/new' autoload :Show, 'qa/page/project/issue/show' autoload :Index, 'qa/page/project/issue/index' + autoload :JiraImport, 'qa/page/project/issue/jira_import' end module Fork @@ -334,6 +355,8 @@ module QA autoload :Edit, 'qa/page/project/wiki/edit' autoload :Show, 'qa/page/project/wiki/show' autoload :GitAccess, 'qa/page/project/wiki/git_access' + autoload :Sidebar, 'qa/page/project/wiki/sidebar' + autoload :List, 'qa/page/project/wiki/list' end module WebIDE @@ -342,6 +365,7 @@ module QA module Snippet autoload :New, 'qa/page/project/snippet/new' + autoload :Show, 'qa/page/project/snippet/show' end end @@ -356,7 +380,6 @@ module QA module Issuable autoload :New, 'qa/page/issuable/new' - autoload :Sidebar, 'qa/page/issuable/sidebar' end module Alert @@ -439,9 +462,13 @@ module QA autoload :ConfirmModal, 'qa/page/component/confirm_modal' autoload :CustomMetric, 'qa/page/component/custom_metric' autoload :DesignManagement, 'qa/page/component/design_management' + autoload :ProjectSelector, 'qa/page/component/project_selector' + autoload :Snippet, 'qa/page/component/snippet' + autoload :NewSnippet, 'qa/page/component/new_snippet' module Issuable autoload :Common, 'qa/page/component/issuable/common' + autoload :Sidebar, 'qa/page/component/issuable/sidebar' end module IssueBoard @@ -460,6 +487,10 @@ module QA autoload :Templates, 'qa/page/component/project/templates' end end + + module Modal + autoload :DeleteWiki, 'qa/page/modal/delete_wiki' + end end ## @@ -555,6 +586,7 @@ module QA autoload :Retrier, 'qa/support/retrier' autoload :Waiter, 'qa/support/waiter' autoload :WaitForRequests, 'qa/support/wait_for_requests' + autoload :OTP, 'qa/support/otp' end end diff --git a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml deleted file mode 100644 index 052ba1c14fb..00000000000 --- a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml +++ /dev/null @@ -1,338 +0,0 @@ -# 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: $AUTO_DEVOPS_DOMAIN - - 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 - tags: - - qa - script: - - check_kube_domain - - install_dependencies - - download_chart - - ensure_namespace - - initialize_tiller - - create_secret - - deploy - 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/login.rb b/qa/qa/flow/login.rb index 8ad303df4de..d4d5cc2dcfc 100644 --- a/qa/qa/flow/login.rb +++ b/qa/qa/flow/login.rb @@ -22,9 +22,9 @@ module QA end end - def sign_in(as: nil, address: :gitlab) + def sign_in(as: nil, address: :gitlab, skip_page_validation: false) Runtime::Browser.visit(address, Page::Main::Login) - Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as) } + Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as, skip_page_validation: skip_page_validation) } end def sign_in_as_admin(address: :gitlab) diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index 43608827b2e..2c8e362edd6 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -118,6 +118,10 @@ module QA run("git push #{uri} #{branch}", max_attempts: 3).to_s end + def push_all_branches + run("git push --all").to_s + end + def merge(branch) run("git merge #{branch}") end diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb index 8629399e911..4c053f1d6a9 100644 --- a/qa/qa/page/component/ci_badge_link.rb +++ b/qa/qa/page/component/ci_badge_link.rb @@ -43,7 +43,7 @@ module QA private def completed?(timeout: 60) - wait_until(reload: false, max_duration: timeout) do + wait_until(reload: false, sleep_interval: 3.0, max_duration: timeout) do COMPLETED_STATUSES.include?(status_badge) end end diff --git a/qa/qa/page/component/dropdown_filter.rb b/qa/qa/page/component/dropdown_filter.rb index a39a04a668d..30c6f13eaf7 100644 --- a/qa/qa/page/component/dropdown_filter.rb +++ b/qa/qa/page/component/dropdown_filter.rb @@ -8,7 +8,7 @@ module QA page.has_css?('.dropdown-input-field', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) find('.dropdown-input-field').set(item) - click_link item + click_on item end end end diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb new file mode 100644 index 00000000000..4e94049efe7 --- /dev/null +++ b/qa/qa/page/component/issuable/sidebar.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Issuable + module Sidebar + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do + element :avatar_image + end + + base.view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do + element :more_assignees_link + end + + base.view 'app/helpers/dropdowns_helper.rb' do + element :dropdown_input_field + end + + base.view 'app/views/shared/issuable/_sidebar.html.haml' do + element :assignee_block + element :dropdown_menu_labels + element :edit_labels_link + element :edit_milestone_link + element :labels_block + element :milestone_block + element :milestone_link + end + end + + def assign_milestone(milestone) + click_element(:edit_milestone_link) + within_element(:milestone_block) do + click_link("#{milestone.title}") + end + + wait_until(reload: false) do + has_element?(:milestone_block, text: milestone.title, wait: 0) + end + + refresh + end + + def click_milestone_link + click_element(:milestone_link) + end + + def has_assignee?(username) + page.within(element_selector_css(:assignee_block)) do + has_text?(username) + end + end + + def has_avatar_image_count?(count) + wait_assignees_block_finish_loading do + all_elements(:avatar_image, count: count) + end + end + + def has_label?(label) + within_element(:labels_block) do + !!has_element?(:label, label_name: label) + end + end + + def has_milestone?(milestone_title) + wait_milestone_block_finish_loading do + has_element?(:milestone_link, title: milestone_title) + end + end + + def more_assignees_link + find_element(:more_assignees_link) + end + + def select_labels_and_refresh(labels) + Support::Retrier.retry_until do + click_element(:edit_labels_link) + has_element?(:dropdown_menu_labels, text: labels.first) + end + + labels.each do |label| + within_element(:dropdown_menu_labels, text: label) do + send_keys_to_element(:dropdown_input_field, [label, :enter]) + end + end + + click_element(:edit_labels_link) + + labels.each do |label| + has_element?(:labels_block, text: label, wait: 0) + end + + refresh + end + + def toggle_more_assignees_link + click_element(:more_assignees_link) + end + + private + + def wait_assignees_block_finish_loading + within_element(:assignee_block) do + wait_until(reload: false, max_duration: 10, sleep_interval: 1) do + finished_loading_block? + yield + end + end + end + + def wait_milestone_block_finish_loading + within_element(:milestone_block) do + wait_until(reload: false, max_duration: 10, sleep_interval: 1) do + finished_loading_block? + yield + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb index 0c840eba7ce..9e843630115 100644 --- a/qa/qa/page/component/issue_board/show.rb +++ b/qa/qa/page/component/issue_board/show.rb @@ -39,11 +39,6 @@ module QA element :boards_list end - view 'app/views/shared/boards/components/_board.html.haml' do - element :board_list - element :board_list_header - end - view 'app/assets/javascripts/boards/toggle_focus.js' do element :focus_mode_button end diff --git a/qa/qa/page/component/new_snippet.rb b/qa/qa/page/component/new_snippet.rb new file mode 100644 index 00000000000..18f2e237097 --- /dev/null +++ b/qa/qa/page/component/new_snippet.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module NewSnippet + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/snippets/components/edit.vue' do + element :snippet_title_field, required: true + element :submit_button + end + + base.view 'app/assets/javascripts/snippets/components/snippet_description_edit.vue' do + element :snippet_description_field + element :description_placeholder, required: true + end + + base.view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do + element :file_name_field + end + + base.view 'app/views/shared/form_elements/_description.html.haml' do + element :issuable_form_description + end + + base.view 'app/views/shared/snippets/_form.html.haml' do + element :snippet_description_field + element :description_placeholder + element :snippet_title_field + element :file_name_field + element :submit_button + end + + base.view 'app/views/shared/_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 + end + + def fill_title(title) + fill_element :snippet_title_field, title + end + + def fill_description(description) + click_element :description_placeholder + fill_element :snippet_description_field, description + end + + def set_visibility(visibility) + choose visibility + end + + def fill_file_name(name) + finished_loading? + fill_element :file_name_field, name + end + + def fill_file_content(content) + finished_loading? + text_area.set content + end + + def click_create_snippet_button + wait_until(reload: false) { !find_element(:submit_button).disabled? } + click_element(:submit_button, Page::Dashboard::Snippet::Show) + end + + private + + def text_area + find('#editor textarea', visible: false) + end + end + end + end +end diff --git a/qa/qa/page/component/project_selector.rb b/qa/qa/page/component/project_selector.rb new file mode 100644 index 00000000000..80ed6b8e53b --- /dev/null +++ b/qa/qa/page/component/project_selector.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module ProjectSelector + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue' do + element :project_search_field + element :project_list_item + end + end + + def fill_project_search_input(project_name) + fill_element :project_search_field, project_name + end + + def select_project + click_element :project_list_item + end + end + end + end +end diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb new file mode 100644 index 00000000000..4ff19c01f1f --- /dev/null +++ b/qa/qa/page/component/snippet.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Snippet + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/assets/javascripts/snippets/components/snippet_title.vue' do + element :snippet_title_content, required: true + end + + base.view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do + element :snippet_description_content + end + + base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do + element :snippet_container + end + + base.view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do + element :file_title_content + end + + base.view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do + element :file_content + end + + base.view 'app/assets/javascripts/blob/components/blob_content.vue' do + element :file_content + end + + base.view 'app/assets/javascripts/snippets/components/snippet_header.vue' do + element :snippet_action_button + element :delete_snippet_button + end + + base.view 'app/assets/javascripts/snippets/components/snippet_blob_view.vue' do + element :clone_button + end + + base.view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do + element :copy_http_url_button + element :copy_ssh_url_button + end + + base.view 'app/views/shared/notes/_comment_button.html.haml' do + element :comment_button + end + + base.view 'app/views/shared/notes/_form.html.haml' do + element :note_field + end + + base.view 'app/views/snippets/notes/_actions.html.haml' do + element :edit_comment_button + end + + base.view 'app/views/shared/notes/_edit_form.html.haml' do + element :edit_note_field + element :save_comment_button + end + + base.view 'app/views/shared/notes/_note.html.haml' do + element :note_content + element :note_author_content + end + + base.view 'app/views/projects/notes/_more_actions_dropdown.html.haml' do + element :more_actions_dropdown + element :delete_comment_button + end + end + + def has_snippet_title?(snippet_title) + has_element? :snippet_title_content, text: snippet_title + end + + def has_snippet_description?(snippet_description) + has_element? :snippet_description_content, text: snippet_description + end + + def has_no_snippet_description? + has_no_element?(:snippet_description_field) + end + + def has_visibility_type?(visibility_type) + within_element(:snippet_container) do + has_text?(visibility_type) + end + end + + def has_file_name?(file_name) + within_element(:file_title_content) do + has_text?(file_name) + end + end + + def has_file_content?(file_content) + finished_loading? + within_element(:file_content) do + has_text?(file_content) + end + end + + def click_edit_button + finished_loading? + click_element(:snippet_action_button, action: 'Edit') + end + + def click_delete_button + finished_loading? + click_element(:snippet_action_button, action: 'Delete') + click_element(:delete_snippet_button) + # wait for the page to reload after deletion + wait_until(reload: false) do + has_no_element?(:delete_snippet_button) && + has_no_element?(:snippet_action_button, action: 'Delete') + end + end + + def get_repository_uri_http + finished_loading? + click_element(:clone_button) + Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s + end + + def get_repository_uri_ssh + finished_loading? + click_element(:clone_button) + Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s + end + + def add_comment(comment) + finished_loading? + fill_element(:note_field, comment) + click_element(:comment_button) + end + + def has_comment_author?(author_username) + finished_loading? + within_element(:note_author_content) do + has_text?('@' + author_username) + end + end + + def has_comment_content?(comment_content) + finished_loading? + within_element(:note_content) do + has_text?(comment_content) + end + end + + def has_syntax_highlighting?(language) + within_element(:file_content) do + find('.line')['lang'].to_s == language + end + end + + def edit_comment(comment) + finished_loading? + click_element(:edit_comment_button) + fill_element(:edit_note_field, comment) + click_element(:save_comment_button) + end + + def delete_comment(comment) + finished_loading? + click_element(:more_actions_dropdown) + accept_alert do + click_element(:delete_comment_button) + end + end + end + end + end +end diff --git a/qa/qa/page/component/web_ide/modal/create_new_file.rb b/qa/qa/page/component/web_ide/modal/create_new_file.rb index 48eb32fefd6..7c55f775476 100644 --- a/qa/qa/page/component/web_ide/modal/create_new_file.rb +++ b/qa/qa/page/component/web_ide/modal/create_new_file.rb @@ -9,7 +9,7 @@ module QA view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do element :file_name_field, required: true element :new_file_modal, required: true - element :template_list, required: true + element :template_list end end end diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb index bcfb4734b59..683e118aa19 100644 --- a/qa/qa/page/dashboard/snippet/new.rb +++ b/qa/qa/page/dashboard/snippet/new.rb @@ -5,70 +5,7 @@ module QA module Dashboard module Snippet class New < Page::Base - view 'app/assets/javascripts/snippets/components/edit.vue' do - element :snippet_title_field, required: true - element :submit_button - end - - view 'app/assets/javascripts/snippets/components/snippet_description_edit.vue' do - element :snippet_description_field - element :description_placeholder, required: true - end - - view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do - element :file_name_field - end - - view 'app/views/shared/form_elements/_description.html.haml' do - element :issuable_form_description - end - - view 'app/views/shared/snippets/_form.html.haml' do - element :snippet_description_field - element :description_placeholder - element :snippet_title_field - element :file_name_field - element :submit_button - end - - view 'app/views/shared/_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_field, title - end - - def fill_description(description) - click_element :description_placeholder - fill_element :snippet_description_field, description - end - - def set_visibility(visibility) - choose visibility - end - - def fill_file_name(name) - finished_loading? - fill_element :file_name_field, name - end - - def fill_file_content(content) - finished_loading? - text_area.set content - end - - def click_create_snippet_button - wait_until(reload: false) { !find_element(:submit_button).disabled? } - click_element :submit_button - end - - private - - def text_area - find('#editor textarea', visible: false) - end + include Page::Component::NewSnippet end end end diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb index da494ae70ec..73e6abe174f 100644 --- a/qa/qa/page/dashboard/snippet/show.rb +++ b/qa/qa/page/dashboard/snippet/show.rb @@ -5,102 +5,7 @@ module QA module Dashboard module Snippet class Show < Page::Base - view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do - element :snippet_description_content - end - - view 'app/assets/javascripts/snippets/components/snippet_title.vue' do - element :snippet_title_content, required: true - end - - view 'app/assets/javascripts/snippets/components/snippet_header.vue' do - element :snippet_container - end - - view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do - element :file_title_content - end - - view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do - element :file_content - end - - view 'app/assets/javascripts/blob/components/blob_content.vue' do - element :file_content - end - - view 'app/assets/javascripts/snippets/components/snippet_header.vue' do - element :snippet_action_button - element :delete_snippet_button - end - - view 'app/assets/javascripts/snippets/components/snippet_blob_view.vue' do - element :clone_button - end - - view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do - element :copy_http_url_button - element :copy_ssh_url_button - end - - def has_snippet_title?(snippet_title) - has_element? :snippet_title_content, text: snippet_title - end - - def has_snippet_description?(snippet_description) - has_element? :snippet_description_content, text: snippet_description - end - - def has_no_snippet_description? - has_no_element?(:snippet_description_field) - end - - def has_visibility_type?(visibility_type) - within_element(:snippet_container) do - has_text?(visibility_type) - end - end - - def has_file_name?(file_name) - within_element(:file_title_content) do - has_text?(file_name) - end - end - - def has_file_content?(file_content) - finished_loading? - within_element(:file_content) do - has_text?(file_content) - end - end - - def click_edit_button - finished_loading? - click_element(:snippet_action_button, action: 'Edit') - end - - def click_delete_button - finished_loading? - click_element(:snippet_action_button, action: 'Delete') - click_element(:delete_snippet_button) - # wait for the page to reload after deletion - wait_until(reload: false) do - has_no_element?(:delete_snippet_button) && - has_no_element?(:snippet_action_button, action: 'Delete') - end - end - - def get_repository_uri_http - finished_loading? - click_element(:clone_button) - Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s - end - - def get_repository_uri_ssh - finished_loading? - click_element(:clone_button) - Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s - end + include Page::Component::Snippet end end end diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index 380984c283e..7689dd7e5c8 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -7,9 +7,11 @@ module QA include SubMenus::Common view 'app/views/layouts/nav/sidebar/_group.html.haml' do - element :group_settings_item - element :group_members_item element :general_settings_link + element :group_issues_item + element :group_members_item + element :group_milestones_link + element :group_settings_item end view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do @@ -44,6 +46,25 @@ module QA end end end + + def go_to_milestones + hover_issues do + within_submenu do + click_element(:group_milestones_link) + end + end + end + + private + + def hover_issues + within_sidebar do + scroll_to_element(:group_issues_item) + find_element(:group_issues_item).hover + + yield + end + end end end end diff --git a/qa/qa/page/group/milestone/index.rb b/qa/qa/page/group/milestone/index.rb new file mode 100644 index 00000000000..a86772a070d --- /dev/null +++ b/qa/qa/page/group/milestone/index.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module QA + module Page + module Group + module Milestone + class Index < Page::Milestone::Index + view 'app/views/groups/milestones/index.html.haml' do + element :new_group_milestone_link + end + + def click_new_milestone_link + click_element(:new_group_milestone_link) + end + end + end + end + end +end diff --git a/qa/qa/page/group/milestone/new.rb b/qa/qa/page/group/milestone/new.rb new file mode 100644 index 00000000000..451117a2163 --- /dev/null +++ b/qa/qa/page/group/milestone/new.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Group + module Milestone + class New < Page::Milestone::New + view 'app/views/groups/milestones/_form.html.haml' do + element :create_milestone_button + element :milestone_description_field + element :milestone_title_field + end + + def click_create_milestone_button + click_element(:create_milestone_button) + end + + def set_description(description) + fill_element(:milestone_description_field, description) + end + + def set_title(title) + fill_element(:milestone_title_field, title) + end + end + end + end + end +end diff --git a/qa/qa/page/group/sub_menus/members.rb b/qa/qa/page/group/sub_menus/members.rb index 33c4caaddcb..895da639c02 100644 --- a/qa/qa/page/group/sub_menus/members.rb +++ b/qa/qa/page/group/sub_menus/members.rb @@ -7,6 +7,10 @@ module QA class Members < Page::Base include Page::Component::UsersSelect + view 'app/assets/javascripts/vue_shared/components/remove_member_modal.vue' do + element :remove_member_modal_content + end + view 'app/views/shared/members/_invite_member.html.haml' do element :member_select_field element :invite_member_button @@ -32,10 +36,12 @@ module QA end def remove_member(username) - page.accept_confirm do - within_element(:member_row, text: username) do - click_element :delete_member_button - end + within_element(:member_row, text: username) do + click_element :delete_member_button + end + + within_element(:remove_member_modal_content) do + click_button("Remove member") end end end diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb deleted file mode 100644 index af5eee35a2d..00000000000 --- a/qa/qa/page/issuable/sidebar.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Issuable - class Sidebar < Page::Base - view 'app/views/shared/issuable/_sidebar.html.haml' do - element :labels_block - element :milestone_block - element :milestone_link - end - - def has_label?(label) - within_element(:labels_block) do - has_element?(:label, label_name: label) - end - end - - def has_milestone?(milestone_title) - within_element(:milestone_block) do - has_element?(:milestone_link, title: milestone_title) - end - end - end - end - end -end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 6af18cb1d2b..416946f44f0 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -22,6 +22,9 @@ module QA element :groups_dropdown, required: true element :more_dropdown element :snippets_link + element :groups_link + element :activity_link + element :milestones_link end view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do @@ -53,10 +56,10 @@ module QA end end - def go_to_snippets + def go_to_more_dropdown_option(option_name) within_top_menu do click_element :more_dropdown - click_element :snippets_link + click_element option_name end end @@ -148,3 +151,5 @@ module QA end end end + +QA::Page::Main::Menu.prepend_if_ee('QA::EE::Page::Main::Menu') diff --git a/qa/qa/page/main/two_factor_auth.rb b/qa/qa/page/main/two_factor_auth.rb new file mode 100644 index 00000000000..003bd8dd1b1 --- /dev/null +++ b/qa/qa/page/main/two_factor_auth.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module QA + module Page + module Main + class TwoFactorAuth < Page::Base + view 'app/views/devise/sessions/two_factor.html.haml' do + element :verify_code_button + element :two_fa_code_field + end + + def click_verify_code_button + click_element :verify_code_button + end + + def set_2fa_code(code) + fill_element(:two_fa_code_field, code) + end + end + end + end +end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 07f8e568b2a..b9a2bf4ee69 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -5,6 +5,7 @@ module QA module MergeRequest class Show < Page::Base include Page::Component::Note + include Page::Component::Issuable::Sidebar view 'app/assets/javascripts/mr_tabs_popover/components/popover.vue' do element :dismiss_popover_button @@ -64,11 +65,6 @@ module QA element :new_diff_line end - view 'app/views/shared/issuable/_sidebar.html.haml' do - element :assignee_block - element :labels_block - end - view 'app/views/projects/merge_requests/_mr_title.html.haml' do element :edit_button end @@ -178,18 +174,6 @@ module QA has_element?(:merge_button) end - def has_assignee?(username) - page.within(element_selector_css(:assignee_block)) do - has_text?(username) - end - end - - def has_label?(label) - within_element(:labels_block) do - !!has_element?(:label, label_name: label) - end - end - def has_pipeline_status?(text) # Pipelines can be slow, so we wait a bit longer than the usual 10 seconds has_element?(:merge_request_pipeline_info_content, text: text, wait: 30) diff --git a/qa/qa/page/milestone/index.rb b/qa/qa/page/milestone/index.rb new file mode 100644 index 00000000000..2e86dd4c0ab --- /dev/null +++ b/qa/qa/page/milestone/index.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Page + module Milestone + class Index < Page::Base + view 'app/views/shared/milestones/_milestone.html.haml' do + element :milestone_link + end + + def click_milestone(milestone) + click_element(:milestone_link, milestone_title: milestone.title) + end + + def has_milestone?(milestone) + has_element?(:milestone_link, milestone_title: milestone.title) + end + end + end + end +end diff --git a/qa/qa/page/milestone/new.rb b/qa/qa/page/milestone/new.rb new file mode 100644 index 00000000000..655254d74fa --- /dev/null +++ b/qa/qa/page/milestone/new.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module QA + module Page + module Milestone + class New < Page::Base + view 'app/views/shared/milestones/_form_dates.html.haml' do + element :due_date_field + element :start_date_field + end + + def set_due_date(due_date) + fill_element(:due_date_field, due_date.to_s + "\n") + end + + def set_start_date(start_date) + fill_element(:start_date_field, start_date.to_s + "\n") + end + end + end + end +end diff --git a/qa/qa/page/milestone/show.rb b/qa/qa/page/milestone/show.rb new file mode 100644 index 00000000000..42efbd4ea30 --- /dev/null +++ b/qa/qa/page/milestone/show.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA + module Page + module Milestone + class Show < Page::Base + include Support::Dates + + view 'app/views/shared/milestones/_description.html.haml' do + element :milestone_description_content + element :milestone_title_content, required: true + end + + view 'app/views/shared/milestones/_sidebar.html.haml' do + element :due_date_content + element :start_date_content + end + + def has_due_date?(due_date) + formatted_due_date = format_date(due_date) + has_element?(:due_date_content, text: formatted_due_date) + end + + def has_start_date?(start_date) + formatted_start_date = format_date(start_date) + has_element?(:start_date_content, text: formatted_start_date) + end + end + end + end +end + +QA::Page::Milestone::Show.prepend_if_ee('QA::EE::Page::Milestone::Show') diff --git a/qa/qa/page/modal/delete_wiki.rb b/qa/qa/page/modal/delete_wiki.rb new file mode 100644 index 00000000000..4f0bc34ee88 --- /dev/null +++ b/qa/qa/page/modal/delete_wiki.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Page + module Modal + class DeleteWiki < Base + view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do + element :confirm_deletion_button, required: true + end + + def confirm_deletion + click_element :confirm_deletion_button + end + end + end + end +end diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb index a3ff5f603fa..b5a4d04b377 100644 --- a/qa/qa/page/profile/two_factor_auth.rb +++ b/qa/qa/page/profile/two_factor_auth.rb @@ -8,9 +8,35 @@ module QA element :configure_it_later_button end + view 'app/views/profiles/two_factor_auths/show.html.haml' do + element :otp_secret_content + element :pin_code_field + element :register_2fa_app_button + end + + view 'app/views/profiles/two_factor_auths/_codes.html.haml' do + element :proceed_button + end + def click_configure_it_later_button click_element :configure_it_later_button end + + def otp_secret_content + find_element(:otp_secret_content).text.gsub('Key:', '').delete(' ') + end + + def set_pin_code(pin_code) + fill_element(:pin_code_field, pin_code) + end + + def click_register_2fa_app_button + click_element :register_2fa_app_button + end + + def click_proceed_button + click_element :proceed_button + end end end end diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index ace2537fc0e..e0c10220fbc 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -18,6 +18,11 @@ module QA element :export_issues_modal end + view 'app/views/projects/issues/import_csv/_button.html.haml' do + element :import_issues_button + element :import_from_jira_link + end + view 'app/views/projects/issues/_issue.html.haml' do element :issue element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern @@ -51,10 +56,25 @@ module QA click_element(:export_issues_button) end + def click_import_from_jira_link + click_element(:import_from_jira_link) + end + + def click_import_issues_dropdown + # When there are no issues, the image that loads causes the buttons to jump + has_loaded_all_images? + click_element(:import_issues_button) + end + def export_issues_modal find_element(:export_issues_modal) end + def go_to_jira_import_form + click_import_issues_dropdown + click_import_from_jira_link + end + def has_assignee_link_count?(count) all_elements(:assignee_link, count: count) end diff --git a/qa/qa/page/project/issue/jira_import.rb b/qa/qa/page/project/issue/jira_import.rb new file mode 100644 index 00000000000..d3be24464ab --- /dev/null +++ b/qa/qa/page/project/issue/jira_import.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Issue + class JiraImport < Page::Base + view 'app/assets/javascripts/jira_import/components/jira_import_form.vue' do + element :jira_project_dropdown + element :jira_issues_import_button + end + + def select_jira_project(jira_project) + select_element(:jira_project_dropdown, jira_project) + end + + def select_project_and_import(jira_project) + select_jira_project(jira_project) + click_element(:jira_issues_import_button) + end + end + end + end + end +end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index dd74ff28763..04f0f34cbbb 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -8,6 +8,7 @@ module QA include Page::Component::Issuable::Common include Page::Component::Note include Page::Component::DesignManagement + include Page::Component::Issuable::Sidebar view 'app/assets/javascripts/notes/components/comment_form.vue' do element :comment_button @@ -23,45 +24,25 @@ module QA element :noteable_note_item end - view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do - element :avatar_image - end - - view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do - element :more_assignees_link - end - view 'app/assets/javascripts/vue_shared/components/issue/related_issuable_item.vue' do element :remove_related_issue_button end - view 'app/helpers/dropdowns_helper.rb' do - element :dropdown_input_field - end - view 'app/views/shared/issuable/_close_reopen_button.html.haml' do element :close_issue_button element :reopen_issue_button end - view 'app/views/shared/issuable/_sidebar.html.haml' do - element :assignee_block - element :labels_block - element :edit_link_labels - element :dropdown_menu_labels - element :milestone_link - end - view 'app/views/shared/notes/_form.html.haml' do element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern end view 'app/views/projects/issues/_tabs.html.haml' do - element :discussion_tab_link - element :discussion_tab_content - element :designs_tab_link element :designs_tab_content + element :designs_tab_link + element :discussion_tab_content + element :discussion_tab_link end def click_discussion_tab @@ -74,10 +55,6 @@ module QA active_element?(:designs_tab_content) end - def click_milestone_link - click_element(:milestone_link) - end - def click_remove_related_issue_button click_element(:remove_related_issue_button) end @@ -90,7 +67,7 @@ module QA # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) method("select_#{filter}_filter").call - fill_element :comment_input, text + fill_element :comment_input, "#{text}\n" unless attachment.nil? QA::Page::Component::Dropzone.new(self, '.new-note') @@ -100,20 +77,10 @@ module QA click_element :comment_button end - def has_avatar_image_count?(count) - wait_assignees_block_finish_loading do - all_elements(:avatar_image, count: count) - end - end - def has_comment?(comment_text) has_element?(:noteable_note_item, text: comment_text, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) end - def more_assignees_link - find_element(:more_assignees_link) - end - def noteable_note_item find_element(:noteable_note_item) end @@ -130,35 +97,6 @@ module QA select_filter_with_text('Show history only') end - def select_labels_and_refresh(labels) - Support::Retrier.retry_until do - click_element(:edit_link_labels) - has_element?(:dropdown_menu_labels, text: labels.first) - end - - labels.each do |label| - within_element(:dropdown_menu_labels, text: label) do - send_keys_to_element(:dropdown_input_field, [label, :enter]) - end - end - - click_element(:edit_link_labels) - - labels.each do |label| - has_element?(:labels_block, text: label, wait: 0) - end - - refresh - end - - def text_of_labels_block - find_element(:labels_block) - end - - def toggle_more_assignees_link - click_element(:more_assignees_link) - end - private def select_filter_with_text(text) @@ -168,15 +106,6 @@ module QA find_element(:filter_options, text: text).click end end - - def wait_assignees_block_finish_loading - within_element(:assignee_block) do - wait_until(reload: false, max_duration: 10, sleep_interval: 1) do - finished_loading_block? - yield - end - end - end end end end diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 971b8c5e5f8..6243dc92b45 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -23,6 +23,9 @@ module QA 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) + job_log = find_element(:job_log_content).text + QA::Runtime::Logger.debug(" \n\n ------- Job log: ------- \n\n #{job_log} \n -------") + passed? end diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index 3d4d0ff9d22..9faf1bd5f8f 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -15,11 +15,14 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :activity_link element :merge_requests_link - element :wiki_link element :snippets_link element :members_link end + view 'app/views/layouts/nav/sidebar/_wiki_link.html.haml' do + element :wiki_link + end + def click_merge_requests within_sidebar do click_element(:merge_requests_link) diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb index 6895c44f72f..d25a3389ae8 100644 --- a/qa/qa/page/project/milestone/index.rb +++ b/qa/qa/page/project/milestone/index.rb @@ -4,13 +4,25 @@ module QA module Page module Project module Milestone - class Index < Page::Base + class Index < Page::Milestone::Index view 'app/views/projects/milestones/index.html.haml' do - element :new_project_milestone + element :new_project_milestone_link end - def click_new_milestone - click_element :new_project_milestone + view 'app/views/shared/milestones/_milestone.html.haml' do + element :milestone_link + end + + def click_new_milestone_link + click_element :new_project_milestone_link + end + + def has_milestone?(milestone) + has_element? :milestone_link, milestone_title: milestone.title + end + + def click_milestone(milestone) + click_element :milestone_link, milestone_title: milestone.title end end end diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb index 751fb141684..98f3a0ef4ab 100644 --- a/qa/qa/page/project/milestone/new.rb +++ b/qa/qa/page/project/milestone/new.rb @@ -4,23 +4,36 @@ module QA module Page module Project module Milestone - class New < Page::Base + class New < Page::Milestone::New view 'app/views/projects/milestones/_form.html.haml' do - element :milestone_create_button - element :milestone_title - element :milestone_description + element :create_milestone_button + element :milestone_description_field + element :milestone_title_field + end + + view 'app/views/shared/milestones/_form_dates.html.haml' do + element :due_date_field + element :start_date_field + end + + def click_create_milestone_button + click_element :create_milestone_button end def set_title(title) - fill_element :milestone_title, title + fill_element :milestone_title_field, title end def set_description(description) - fill_element :milestone_description, description + fill_element :milestone_description_field, description + end + + def set_due_date(due_date) + fill_element :due_date_field, due_date.to_s + "\n" end - def click_milestone_create_button - click_element :milestone_create_button + def set_start_date(start_date) + fill_element :start_date_field, start_date.to_s + "\n" end end end diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb index 46fddfa6078..e1612718883 100644 --- a/qa/qa/page/project/operations/kubernetes/show.rb +++ b/qa/qa/page/project/operations/kubernetes/show.rb @@ -29,6 +29,14 @@ module QA element :uninstall_button end + view 'app/views/clusters/clusters/_health.html.haml' do + element :cluster_health_section + end + + view 'app/views/clusters/clusters/_health_tab.html.haml' do + element :health, required: true + end + def open_details has_element?(:details, wait: 30) click_element :details @@ -71,11 +79,32 @@ module QA def save_domain click_element :save_changes_button, Page::Project::Operations::Kubernetes::Show end + + def wait_for_cluster_health + wait_until(max_duration: 120, sleep_interval: 3, reload: true) do + has_cluster_health_graphs? + end + end + + def open_health + has_element?(:health, wait: 30) + click_element :health + end + + def has_cluster_health_graphs? + within_cluster_health_section do + has_text?('CPU Usage') + end + end + + def within_cluster_health_section + within_element :cluster_health_section do + yield + end + end end end end end end end - -QA::Page::Project::Operations::Kubernetes::Show.prepend_if_ee('QA::EE::Page::Project::Operations::Kubernetes::Show') diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb index a1c15e72f44..e9e4923a0e2 100644 --- a/qa/qa/page/project/operations/metrics/show.rb +++ b/qa/qa/page/project/operations/metrics/show.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'securerandom' + module QA module Page module Project @@ -60,7 +62,7 @@ module QA def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to master branch') click_element :dashboards_filter_dropdown click_on 'Duplicate dashboard' - fill_element :duplicate_dashboard_filename_field, save_as + fill_element :duplicate_dashboard_filename_field, "#{SecureRandom.hex(8)}-#{save_as}" choose commit_option within('.modal-content') { click_button(class: 'btn-success') } end diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 54e4d0fb2fc..aa2ef2f058f 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -5,11 +5,11 @@ module QA module Project module Pipeline class Index < QA::Page::Base - view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do + view 'app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue' do element :pipeline_url_link end - view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do + view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue' do element :pipeline_commit_status element :pipeline_retry_button end diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index d22dfefc096..43003fe1953 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -22,6 +22,7 @@ module QA view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do element :linked_pipeline_button + element :child_pipeline end view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do @@ -60,6 +61,10 @@ module QA end end + def has_child_pipeline? + has_element? :child_pipeline + end + def click_job(job_name) click_element(:job_link, text: job_name) end diff --git a/qa/qa/page/project/settings/incidents.rb b/qa/qa/page/project/settings/incidents.rb index 94d5fc369ad..9b523e2aa9e 100644 --- a/qa/qa/page/project/settings/incidents.rb +++ b/qa/qa/page/project/settings/incidents.rb @@ -5,10 +5,11 @@ module QA module Project module Settings class Incidents < Page::Base - view 'app/views/projects/settings/operations/_incidents.html.haml' do + view 'app/assets/javascripts/incidents_settings/components/alerts_form.vue' do element :create_issue_checkbox element :incident_templates_dropdown element :save_changes_button + element :incident_templates_item end def enable_issues_for_incidents @@ -16,8 +17,9 @@ module QA end def select_issue_template(template) + click_element(:incident_templates_dropdown) within_element :incident_templates_dropdown do - find(:option, template).select_option + find_element(:incident_templates_item, text: template).click end end diff --git a/qa/qa/page/project/settings/operations.rb b/qa/qa/page/project/settings/operations.rb index f6e005d3189..b39b8f92cc7 100644 --- a/qa/qa/page/project/settings/operations.rb +++ b/qa/qa/page/project/settings/operations.rb @@ -7,7 +7,7 @@ module QA class Operations < Page::Base include QA::Page::Settings::Common - view 'app/views/projects/settings/operations/_incidents.html.haml' do + view 'app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue' do element :incidents_settings_content end diff --git a/qa/qa/page/project/settings/protected_tags.rb b/qa/qa/page/project/settings/protected_tags.rb new file mode 100644 index 00000000000..bf8f349cfd5 --- /dev/null +++ b/qa/qa/page/project/settings/protected_tags.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + class ProtectedTags < Page::Base + include Page::Component::DropdownFilter + + view 'app/views/projects/protected_tags/shared/_dropdown.html.haml' do + element :tags_dropdown + end + + view 'app/views/projects/protected_tags/_create_protected_tag.html.haml' do + element :access_levels_content + element :access_levels_dropdown + end + + view 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml' do + element :protect_tag_button + end + + def set_tag(tag_name) + click_element :tags_dropdown + filter_and_select(tag_name) + end + + def choose_access_level_role(role) + return if find_element(:access_levels_dropdown).text == role + + click_element :access_levels_dropdown + within_element(:access_levels_content) do + click_on role + end + end + + def click_protect_tag_button + click_element :protect_tag_button + end + end + end + end + end +end + +QA::Page::Project::Settings::ProtectedTags.prepend_if_ee('QA::EE::Page::Project::Settings::ProtectedTags') diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index 8e9a24a4741..fd3a590c2c1 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -23,6 +23,10 @@ module QA element :deploy_keys_settings end + view 'app/views/projects/protected_tags/shared/_index.html.haml' do + element :protected_tag_settings_content + end + def expand_deploy_tokens(&block) expand_section(:deploy_tokens_settings) do Settings::DeployTokens.perform(&block) @@ -46,6 +50,12 @@ module QA MirroringRepositories.perform(&block) end end + + def expand_protected_tags(&block) + expand_section(:protected_tag_settings_content) do + ProtectedTags.perform(&block) + end + end end end end diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb index adc92b4acaf..af4dbb08430 100644 --- a/qa/qa/page/project/settings/runners.rb +++ b/qa/qa/page/project/settings/runners.rb @@ -13,7 +13,7 @@ module QA ## # TODO, phase-out CSS classes added in Ruby helpers. # - view 'app/helpers/runners_helper.rb' do + view 'app/helpers/ci/runners_helper.rb' do # rubocop:disable Lint/InterpolationCheck element :runner_status, 'runner-status-#{status}' # rubocop:disable QA/ElementWithPattern # rubocop:enable Lint/InterpolationCheck diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb index 9f9331bac94..75baedb1b0f 100644 --- a/qa/qa/page/project/settings/services/jira.rb +++ b/qa/qa/page/project/settings/services/jira.rb @@ -6,11 +6,11 @@ module QA module Settings module Services class Jira < QA::Page::Base - view 'app/views/shared/_field.html.haml' do - element :url_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern - element :username_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern - element :password_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern - element :jira_issue_transition_id_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern + view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do + element :service_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern + element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern + element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern + element :service_jira_issue_transition_id_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern end view 'app/helpers/services_helper.rb' do @@ -34,19 +34,19 @@ module QA private def set_jira_server_url(url) - fill_element(:url_field, url) + fill_element(:service_url_field, url) end def set_username(username) - fill_element(:username_field, username) + fill_element(:service_username_field, username) end def set_password(password) - fill_element(:password_field, password) + fill_element(:service_password_field, password) end def set_transaction_ids(transaction_ids) - fill_element(:jira_issue_transition_id_field, transaction_ids) + fill_element(:service_jira_issue_transition_id_field, transaction_ids) end def click_save_changes_button diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 00298ff9fb5..2354a0d9332 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -51,9 +51,12 @@ module QA element :quick_actions end - view 'app/views/projects/tree/_tree_header.html.haml' do + view 'app/assets/javascripts/repository/components/breadcrumbs.vue' do element :add_to_tree element :new_file_option + end + + view 'app/assets/javascripts/repository/components/web_ide_link.vue' do element :web_ide_button end diff --git a/qa/qa/page/project/snippet/new.rb b/qa/qa/page/project/snippet/new.rb index 1463dfc2c7f..7431d6c1bf8 100644 --- a/qa/qa/page/project/snippet/new.rb +++ b/qa/qa/page/project/snippet/new.rb @@ -4,7 +4,8 @@ module QA module Page module Project module Snippet - class New < Page::Dashboard::Snippet::New + class New < Page::Base + include Page::Component::NewSnippet include Component::LazyLoader view 'app/views/shared/empty_states/_snippets.html.haml' do element :create_first_snippet_link diff --git a/qa/qa/page/project/snippet/show.rb b/qa/qa/page/project/snippet/show.rb new file mode 100644 index 00000000000..f66fa2cbe51 --- /dev/null +++ b/qa/qa/page/project/snippet/show.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Snippet + class Show < Page::Base + include Page::Component::Snippet + + view 'app/views/projects/notes/_actions.html.haml' do + element :edit_comment_button + end + end + end + end + end +end diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb index c15a8ec4cc7..124faf0d346 100644 --- a/qa/qa/page/project/sub_menus/issues.rb +++ b/qa/qa/page/project/sub_menus/issues.rb @@ -50,6 +50,14 @@ module QA end end + def go_to_milestones + hover_issues do + within_submenu do + click_element(:milestones_link) + end + end + end + private def hover_issues diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb index 38d6b8e50f4..c78c7521b64 100644 --- a/qa/qa/page/project/sub_menus/repository.rb +++ b/qa/qa/page/project/sub_menus/repository.rb @@ -14,15 +14,16 @@ module QA include QA::Page::Project::SubMenus::Common view 'app/views/layouts/nav/sidebar/_project.html.haml' do - element :project_menu_repo + element :repository_link element :branches_link + element :tags_link end end end def click_repository within_sidebar do - click_element(:project_menu_repo) + click_element(:repository_link) end end @@ -34,11 +35,19 @@ module QA end end + def go_to_repository_tags + hover_repository do + within_submenu do + click_element(:tags_link) + end + end + end + private def hover_repository within_sidebar do - find_element(:project_menu_repo).hover + find_element(:repository_link).hover yield end diff --git a/qa/qa/page/project/tag/index.rb b/qa/qa/page/project/tag/index.rb new file mode 100644 index 00000000000..b8f7bd3b0b4 --- /dev/null +++ b/qa/qa/page/project/tag/index.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Tag + class Index < Page::Base + view 'app/views/projects/tags/index.html.haml' do + element :new_tag_button + end + + def click_new_tag_button + click_element :new_tag_button + end + end + end + end + end +end diff --git a/qa/qa/page/project/tag/new.rb b/qa/qa/page/project/tag/new.rb new file mode 100644 index 00000000000..dc59c07ec98 --- /dev/null +++ b/qa/qa/page/project/tag/new.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Tag + class New < Page::Base + view 'app/views/projects/tags/new.html.haml' do + element :tag_name_field + element :tag_message_field + element :release_notes_field + element :create_tag_button + end + + view 'app/views/shared/_zen.html.haml' do + # This partial adds the `release_notes_field` selector passed from 'app/views/projects/tags/new.html.haml' + # The checks below ensure that required lines are not removed without updating this page object + element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern + element :_, "text_area_tag attr, current_text, data: { qa_selector: qa_selector }" # rubocop:disable QA/ElementWithPattern + end + + def fill_tag_name(text) + fill_element(:tag_name_field, text) + end + + def fill_tag_message(text) + fill_element(:tag_message_field, text) + end + + def fill_release_notes(text) + fill_element(:release_notes_field, text) + end + + def click_create_tag_button + click_element :create_tag_button + end + end + end + end + end +end diff --git a/qa/qa/page/project/tag/show.rb b/qa/qa/page/project/tag/show.rb new file mode 100644 index 00000000000..1974448a7c5 --- /dev/null +++ b/qa/qa/page/project/tag/show.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Tag + class Show < Page::Base + view 'app/views/projects/tags/show.html.haml' do + element :tag_name_content + element :tag_message_content + element :tag_release_notes_content + end + + def has_tag_name?(text) + has_element?(:tag_name_content, text: text) + end + + def has_tag_message?(text) + has_element?(:tag_message_content, text: text) + end + + def has_tag_release_notes?(text) + has_element?(:tag_release_notes_content, text: text) + end + end + end + end + end +end diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb index 29f431d81df..b46d2d32f1f 100644 --- a/qa/qa/page/project/web_ide/edit.rb +++ b/qa/qa/page/project/web_ide/edit.rb @@ -50,6 +50,15 @@ module QA element :first_file_button end + view 'app/assets/javascripts/vue_shared/components/file_row.vue' do + element :file_name_content + end + + view 'app/assets/javascripts/ide/components/new_dropdown/index.vue' do + element :dropdown_button + element :rename_move_button + end + def has_file?(file_name) within_element(:file_list) do page.has_content? file_name @@ -132,6 +141,14 @@ module QA fill_element(:file_name_field, file_name) click_button('Create file') end + + def rename_file(file_name, new_file_name) + click_element(:file_name_content, text: file_name) + click_element(:dropdown_button) + click_element(:rename_move_button, Page::Component::WebIDE::Modal::CreateNewFile) + fill_element(:file_name_field, new_file_name) + click_button('Rename file') + end end end end diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb index 96301e33733..6f3be904eb3 100644 --- a/qa/qa/page/project/wiki/edit.rb +++ b/qa/qa/page/project/wiki/edit.rb @@ -4,7 +4,9 @@ module QA module Page module Project module Wiki - class Edit < Page::Base + class Edit < Base + include Wiki::Sidebar + view 'app/views/shared/wikis/_form.html.haml' do element :wiki_title_textbox element :wiki_content_textarea @@ -13,6 +15,10 @@ module QA element :create_page_button end + view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do + element :delete_button + end + def set_title(title) fill_element :wiki_title_textbox, title end @@ -32,6 +38,11 @@ module QA def click_create_page click_element :create_page_button end + + def delete_page + click_element :delete_button, Page::Modal::DeleteWiki + Page::Modal::DeleteWiki.perform(&:confirm_deletion) + end end end end diff --git a/qa/qa/page/project/wiki/list.rb b/qa/qa/page/project/wiki/list.rb new file mode 100644 index 00000000000..785847011bf --- /dev/null +++ b/qa/qa/page/project/wiki/list.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Wiki + class List < Base + view 'app/views/shared/wikis/_pages_wiki_page.html.haml' do + element :wiki_page_link + end + + def click_page_link(page_title) + click_element :wiki_page_link, page_name: page_title + end + + def has_page_listed?(page_title) + has_element? :wiki_page_link, page_name: page_title + end + end + end + end + end +end diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index 7e4e714f4a6..cdd18e420d1 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -4,13 +4,10 @@ module QA module Page module Project module Wiki - class Show < Page::Base + class Show < Base + include Wiki::Sidebar include Component::LazyLoader - view 'app/views/shared/wikis/_sidebar.html.haml' do - element :clone_repository_link - end - view 'app/views/shared/wikis/show.html.haml' do element :wiki_page_title element :wiki_page_content @@ -54,14 +51,6 @@ module QA click_element(:edit_page_button) end - def click_clone_repository - click_element(:clone_repository_link) - end - - def wiki_text - find_element(:wiki_page_content).text - end - def has_title?(title) has_element?(:wiki_page_title, title) end @@ -69,6 +58,10 @@ module QA def has_content?(content) has_element?(:wiki_page_content, content) end + + def has_no_page? + has_element? :create_first_page_link + end end end end diff --git a/qa/qa/page/project/wiki/sidebar.rb b/qa/qa/page/project/wiki/sidebar.rb new file mode 100644 index 00000000000..dc27c23e4c3 --- /dev/null +++ b/qa/qa/page/project/wiki/sidebar.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Wiki + module Sidebar + extend QA::Page::PageConcern + + def self.included(base) + super + + base.view 'app/views/shared/wikis/_sidebar.html.haml' do + element :clone_repository_link + element :view_all_pages_button + end + + base.view 'app/views/shared/wikis/_sidebar_wiki_page.html.haml' do + element :wiki_page_link + end + end + + def click_clone_repository + click_element(:clone_repository_link) + end + + def click_view_all_pages + click_element(:view_all_pages_button) + end + + def click_page_link(page_title) + click_element :wiki_page_link, page_name: page_title + end + + def has_page_listed?(page_title) + has_element? :wiki_page_link, page_name: page_title + end + end + end + end + end +end diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 591aa449219..babdfc96265 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -83,13 +83,13 @@ module QA end def api_get_from(get_path) - url = Runtime::API::Request.new(api_client, get_path).url - response = get(url) + request = Runtime::API::Request.new(api_client, get_path) + response = get(request.url) if response.code == HTTP_STATUS_SERVER_ERROR - raise InternalServerError, "Failed to GET #{url} - (#{response.code}): `#{response}`." + raise InternalServerError, "Failed to GET #{request.mask_url} - (#{response.code}): `#{response}`." elsif response.code != HTTP_STATUS_OK - raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`." + raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`." end response @@ -108,11 +108,11 @@ module QA end def api_delete - url = Runtime::API::Request.new(api_client, api_delete_path).url - response = delete(url) + request = Runtime::API::Request.new(api_client, api_delete_path) + response = delete(request.url) unless [HTTP_STATUS_NO_CONTENT, HTTP_STATUS_ACCEPTED].include? response.code - raise ResourceNotDeletedError, "Resource at #{url} could not be deleted (#{response.code}): `#{response}`." + raise ResourceNotDeletedError, "Resource at #{request.mask_url} could not be deleted (#{response.code}): `#{response}`." end response diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 850d6205305..75dcb4db55f 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -59,6 +59,10 @@ module QA "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}" end + def api_put_path + "/groups/#{id}" + end + def api_post_path '/groups' end @@ -75,6 +79,15 @@ module QA def api_delete_path "/groups/#{id}" end + + def set_require_two_factor_authentication(value:) + put_body = { require_two_factor_authentication: value } + response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body + + unless response.code == HTTP_STATUS_OK + raise ResourceUpdateFailedError, "Could not update require_two_factor_authentication to #{value}. Request returned (#{response.code}): `#{response}`." + end + end end end end diff --git a/qa/qa/resource/group_milestone.rb b/qa/qa/resource/group_milestone.rb new file mode 100644 index 00000000000..1fb07fdbd0b --- /dev/null +++ b/qa/qa/resource/group_milestone.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module QA + module Resource + class GroupMilestone < Base + attr_writer :start_date, :due_date + + attribute :id + attribute :title + attribute :description + + attribute :group do + Group.fabricate_via_api! do |resource| + resource.name = 'group-with-milestone' + end + end + + def initialize + @title = "group-milestone-#{SecureRandom.hex(4)}" + @description = "My awesome group milestone." + end + + def api_get_path + "/groups/#{group.id}/milestones/#{id}" + end + + def api_post_path + "/groups/#{group.id}/milestones" + end + + def api_post_body + { + title: title, + description: description + }.tap do |hash| + hash[:start_date] = @start_date if @start_date + hash[:due_date] = @due_date if @due_date + end + end + + def fabricate! + group.visit! + + Page::Group::Menu.perform(&:go_to_milestones) + Page::Group::Milestone::Index.perform(&:click_new_milestone_link) + + Page::Group::Milestone::New.perform do |new_milestone| + new_milestone.set_title(@title) + new_milestone.set_description(@description) + new_milestone.set_start_date(@start_date) if @start_date + new_milestone.set_due_date(@due_date) if @due_date + new_milestone.click_create_milestone_button + end + end + end + end +end diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index b4295a35263..d96d8d744d2 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -34,6 +34,7 @@ module QA Page::Project::Issue::New.perform do |new_page| new_page.fill_title(@title) new_page.fill_description(@description) + new_page.choose_milestone(@milestone) if @milestone new_page.create_new_issue end end diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index 38a620a5427..4ebed37ca23 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -8,10 +8,14 @@ module QA # module Members def add_member(user, access_level = AccessLevel::DEVELOPER) + QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}]) + post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level } end def remove_member(user) + QA::Runtime::Logger.debug(%Q[Removing user #{user.username} from #{full_path} #{self.class.name}]) + delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 645f4e97ee0..358e87b0eb9 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -96,7 +96,11 @@ module QA end def has_file?(file_path) - repository_tree.any? { |file| file[:path] == file_path } + response = repository_tree + + raise ResourceNotFoundError, "#{response[:message]}" if response.is_a?(Hash) && response.has_key?(:message) + + response.any? { |file| file[:path] == file_path } end def api_get_path diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index 385b9f0c96b..c9218e03e35 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -7,6 +7,7 @@ module QA attribute :id attribute :title + attribute :description attribute :project do Project.fabricate_via_api! do |resource| @@ -16,6 +17,7 @@ module QA def initialize @title = "project-milestone-#{SecureRandom.hex(4)}" + @description = "My awesome project milestone." end def api_get_path @@ -28,12 +30,28 @@ module QA def api_post_body { - title: title + title: title, + description: description }.tap do |hash| hash[:start_date] = @start_date if @start_date hash[:due_date] = @due_date if @due_date end end + + def fabricate! + project.visit! + + Page::Project::Menu.perform(&:go_to_milestones) + Page::Project::Milestone::Index.perform(&:click_new_milestone_link) + + Page::Project::Milestone::New.perform do |new_milestone| + new_milestone.set_title(@title) + new_milestone.set_description(@description) + new_milestone.set_start_date(@start_date) if @start_date + new_milestone.set_due_date(@due_date) if @due_date + new_milestone.click_create_milestone_button + end + end end end end diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb index e3fb5bf486d..3243eacdb28 100644 --- a/qa/qa/resource/repository/commit.rb +++ b/qa/qa/resource/repository/commit.rb @@ -29,6 +29,30 @@ module QA @add_files = files end + def add_directory(dir) + raise "Must set directory as a Pathname" unless dir.is_a?(Pathname) + + files_to_add = [] + + dir.each_child do |child| + case child.ftype? + when "file" + files_to_add.append({ + file_path: child.to_s, + content: child.read + }) + when "directory" + add_directory(child) + else + continue + end + end + + validate_files!(files_to_add) + + @add_files.merge(files_to_add) + end + def update_files(files) validate_files!(files) diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 032ff65c58b..b351d92092f 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -14,6 +14,7 @@ module QA attribute :id attribute :runners_token attribute :name + attribute :full_path def initialize @path = Runtime::Namespace.sandbox_name diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index e6dbe3faa61..41908a71cf9 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -117,7 +117,10 @@ module QA user.password = password end else - self.fabricate! + self.fabricate! do |user| + user.username = username if username + user.password = password if password + end end end diff --git a/qa/qa/resource/wiki/project_page.rb b/qa/qa/resource/wiki/project_page.rb index 5d0a0a37765..8bcc4bfe220 100644 --- a/qa/qa/resource/wiki/project_page.rb +++ b/qa/qa/resource/wiki/project_page.rb @@ -34,7 +34,7 @@ module QA rescue ResourceURLMissingError # TODO # workaround - project.web_url.concat("/-/wikis/#{slug}") + "#{project.web_url}/-/wikis/#{slug}" end def api_get_path diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 677fba7ced7..804ebf27851 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'gitlab/qa' +require 'uri' module QA module Runtime @@ -23,10 +24,39 @@ module QA SUPPORTED_FEATURES end - def dot_com? - Runtime::Scenario.gitlab_address.include?(".com") + def address_matches?(*options) + return false unless Runtime::Scenario.attributes[:gitlab_address] + + opts = {} + opts[:domain] = '.+' + opts[:tld] = '.com' + + uri = URI(Runtime::Scenario.gitlab_address) + + if options.any? + options.each do |option| + opts[:domain] = 'gitlab' if option == :production + + if option.is_a?(Hash) && !option[:subdomain].nil? + opts.merge!(option) + + opts[:subdomain] = case option[:subdomain] + when Array + "(#{option[:subdomain].join("|")})." + when Regexp + option[:subdomain] + else + "(#{option[:subdomain]})." + end + end + end + end + + uri.host.match?(/^#{opts[:subdomain]}#{opts[:domain]}#{opts[:tld]}$/) end + alias_method :dot_com?, :address_matches? + def additional_repository_storage ENV['QA_ADDITIONAL_REPOSITORY_STORAGE'] end @@ -194,6 +224,14 @@ module QA ENV['GITLAB_QA_PASSWORD_6'] end + def gitlab_qa_2fa_owner_username_1 + ENV['GITLAB_QA_2FA_OWNER_USERNAME_1'] || 'gitlab-qa-2fa-owner-user1' + end + + def gitlab_qa_2fa_owner_password_1 + ENV['GITLAB_QA_2FA_OWNER_PASSWORD_1'] + end + def gitlab_qa_1p_email ENV['GITLAB_QA_1P_EMAIL'] end diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index d8fa72456ad..a0433689e99 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -6,21 +6,177 @@ module QA include Service::Shellout def initialize + @gitlab = 'gitlab-gitaly-ha' @praefect = 'praefect' - @first_node = 'gitaly1' - @second_node = 'gitaly2' - @primary_node = @first_node - @secondary_node = @second_node + @postgres = 'postgres' + @primary_node = 'gitaly1' + @secondary_node = 'gitaly2' + @tertiary_node = 'gitaly3' + @virtual_storage = 'default' end - def stop_primary_node - shell "docker stop #{@primary_node}" - @secondary_node, @primary_node = @primary_node, @secondary_node + def enable_writes + shell "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml enable-writes -virtual-storage #{@virtual_storage}'" end - def reset - shell "docker start #{@primary_node}" - shell "docker start #{@secondary_node}" + def replicated?(project_id) + shell %(docker exec gitlab-gitaly-ha bash -c 'gitlab-rake "gitlab:praefect:replicas[#{project_id}]"') do |line| + # The output of the rake task looks something like this: + # + # Project name | gitaly1 (primary) | gitaly2 | gitaly3 + # ---------------------------------------------------------------------------------------------------------------------------------------------------------------- + # gitaly_cluster-3aff1f2bd14e6c98 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619 | 23c4422629234d62b62adacafd0a33a8364e8619 + # + # We want to confirm that the checksums are identical + break line.split('|').map(&:strip)[1..3].uniq.one? if line.start_with?("gitaly_cluster") + end + end + + def start_praefect + start_node(@praefect) + end + + def stop_praefect + stop_node(@praefect) + end + + def start_node(name) + shell "docker start #{name}" + end + + def stop_node(name) + shell "docker stop #{name}" + end + + def trigger_failover_by_stopping_primary_node + stop_node(@primary_node) + end + + def clear_replication_queue + QA::Runtime::Logger.debug("Clearing the replication queue") + shell <<~CMD + docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \ + bash -c "psql -U postgres -d praefect_production -h postgres.test \ + -c \\"delete from replication_queue_job_lock; delete from replication_queue_lock; delete from replication_queue;\\"" + CMD + end + + def create_stalled_replication_queue + QA::Runtime::Logger.debug("Setting jobs in replication queue to `in_progress` and acquiring locks") + shell <<~CMD + docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \ + bash -c "psql -U postgres -d praefect_production -h postgres.test \ + -c \\"update replication_queue set state = 'in_progress'; + insert into replication_queue_job_lock (job_id, lock_id, triggered_at) + select id, rq.lock_id, created_at from replication_queue rq + left join replication_queue_job_lock rqjl on rq.id = rqjl.job_id + where state = 'in_progress' and rqjl.job_id is null; + update replication_queue_lock set acquired = 't';\\"" + CMD + end + + def replication_queue_lock_count + result = [] + cmd = <<~CMD + docker exec --env PGPASSWORD=SQL_PASSWORD #{@postgres} \ + bash -c "psql -U postgres -d praefect_production -h postgres.test \ + -c \\"select count(*) from replication_queue_lock where acquired = 't';\\"" + CMD + shell cmd do |line| + result << line + end + # The result looks like: + # count + # ----- + # 1 + result[2].to_i + end + + def reset_cluster + start_node(@praefect) + start_node(@primary_node) + start_node(@secondary_node) + start_node(@tertiary_node) + enable_writes + end + + def wait_for_praefect + wait_until_shell_command_matches( + "docker exec #{@praefect} bash -c 'cat /var/log/gitlab/praefect/current'", + /listening at tcp address/ + ) + end + + def wait_for_sql_ping + wait_until_shell_command_matches( + "docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping'", + /praefect sql-ping: OK/ + ) + end + + def wait_for_storage_nodes + nodes_confirmed = { + @primary_node => false, + @secondary_node => false, + @tertiary_node => false + } + + wait_until_shell_command("docker exec #{@praefect} bash -c '/opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml dial-nodes'") do |line| + QA::Runtime::Logger.info(line.chomp) + + nodes_confirmed.each_key do |node| + nodes_confirmed[node] = true if line =~ /SUCCESS: confirmed Gitaly storage "#{node}" in virtual storages \[#{@virtual_storage}\] is served/ + end + + nodes_confirmed.values.all? + end + end + + def wait_for_gitaly_check + storage_ok = false + check_finished = false + + wait_until_shell_command("docker exec #{@gitlab} bash -c 'gitlab-rake gitlab:gitaly:check'") do |line| + QA::Runtime::Logger.info(line.chomp) + + storage_ok = true if line =~ /Gitaly: ... #{@virtual_storage} ... OK/ + check_finished = true if line =~ /Checking Gitaly ... Finished/ + + storage_ok && check_finished + end + end + + def wait_for_gitlab_shell_check + wait_until_shell_command_matches( + "docker exec #{@gitlab} bash -c 'gitlab-rake gitlab:gitlab_shell:check'", + /Checking GitLab Shell ... Finished/ + ) + end + + def wait_for_reliable_connection + wait_for_praefect + wait_for_sql_ping + wait_for_storage_nodes + wait_for_gitaly_check + wait_for_gitlab_shell_check + end + + private + + def wait_until_shell_command(cmd) + Support::Waiter.wait_until do + shell cmd do |line| + break true if yield line + end + end + end + + def wait_until_shell_command_matches(cmd, regex) + wait_until_shell_command(cmd) do |line| + QA::Runtime::Logger.info(line.chomp) + + line =~ regex + end end end end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 217df669db3..6efe50c4ae2 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -19,6 +19,13 @@ module QA Open3.popen2e(*command) do |stdin, out, wait| stdin.puts(stdin_data) if stdin_data stdin.close if stdin_data + + if block_given? + out.each do |line| + yield line + end + end + out.each_char { |char| print char } if wait.value.exited? && wait.value.exitstatus.nonzero? diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb index 1bf435014af..d836bdde9d5 100644 --- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb +++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb @@ -3,7 +3,7 @@ require 'airborne' module QA - context 'Manage with IP rate limits', :requires_admin do + RSpec.describe 'Manage with IP rate limits', :requires_admin do describe 'Users API' do let(:api_client) { Runtime::API::Client.new(:gitlab, ip_limits: true) } let(:request) { Runtime::API::Request.new(api_client, '/users') } diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb index fbc26e81b69..0bd52cbdaa4 100644 --- a/qa/qa/specs/features/api/1_manage/users_spec.rb +++ b/qa/qa/specs/features/api/1_manage/users_spec.rb @@ -3,13 +3,10 @@ require 'airborne' module QA - context 'Manage' do + RSpec.describe 'Manage' do describe 'Users API' do - before(:context) do - @api_client = Runtime::API::Client.new(:gitlab) - end - - let(:request) { Runtime::API::Request.new(@api_client, '/users') } + let(:api_client) { Runtime::API::Client.new(:gitlab) } + let(:request) { Runtime::API::Request.new(api_client, '/users') } it 'GET /users' do get request.url diff --git a/qa/qa/specs/features/api/2_plan/.gitkeep b/qa/qa/specs/features/api/2_plan/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/qa/qa/specs/features/api/2_plan/.gitkeep +++ /dev/null diff --git a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb index 58d716f759e..d72389f1d9d 100644 --- a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb +++ b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb @@ -3,7 +3,7 @@ require 'airborne' module QA - context 'Plan' do + RSpec.describe 'Plan' do include Support::Api describe 'Issue' do diff --git a/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb index d5ab6a3544d..11e7db5b097 100644 --- a/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module QA - context 'Create' do - describe 'Changing Gitaly repository storage', :orchestrated, :requires_admin do + RSpec.describe 'Create' do + describe 'Changing Gitaly repository storage', :requires_admin do shared_examples 'repository storage move' do it 'confirms a `finished` status after moving project repository storage' do expect(project).to have_file('README.md') @@ -24,7 +24,7 @@ module QA end end - context 'when moving from one Gitaly storage to another', :repository_storage do + context 'when moving from one Gitaly storage to another', :orchestrated, :repository_storage do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'repo-storage-move-status' @@ -36,7 +36,9 @@ module QA it_behaves_like 'repository storage move' end - context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect do + # Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect + # scenario with other tests that aren't considered orchestrated. + context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/227127', type: :investigating } do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'repo-storage-move' diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb index 92858ba4107..1a74b2c9da7 100644 --- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb @@ -4,7 +4,7 @@ require 'airborne' require 'securerandom' module QA - describe 'API basics' do + RSpec.describe 'API basics' do before(:context) do @api_client = Runtime::API::Client.new(:gitlab) end diff --git a/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb new file mode 100644 index 00000000000..a4040a46b84 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/repository/praefect_replication_queue_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'parallel' + +module QA + RSpec.describe 'Create' do + context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_ha, :skip_live_env do + let(:praefect_manager) { Service::PraefectManager.new } + let(:project) do + Resource::Project.fabricate! do |project| + project.name = "gitaly_cluster" + project.initialize_with_readme = true + end + end + + after do + praefect_manager.reset_cluster + praefect_manager.clear_replication_queue + end + + it 'allows replication of different repository after interruption' do + # We want to fill the replication queue with 10 `in_progress` jobs, + # while a lock has been acquired, which is when the problem occurred + # as reported in https://gitlab.com/gitlab-org/gitaly/-/issues/2801 + # + # We'll do this by creating 10 branches and pushing them all at once, + # and then stop Praefect when a lock is acquired, set all the jobs + # to `in_progress`, and create a job lock for each one. + queue_size_target = 10 + + Git::Repository.perform do |repository| + repository.uri = project.repository_http_location.uri + repository.use_default_credentials + repository.clone + repository.configure_identity('GitLab QA', 'root@gitlab.com') + 1.upto(queue_size_target) do |i| + repository.checkout("branch#{i}", new_branch: true) + repository.commit_file("file#{i}", SecureRandom.random_bytes(10000000), "Add file#{i}") + end + repository.push_all_branches + end + + count = 0 + while count < 1 + count = praefect_manager.replication_queue_lock_count + QA::Runtime::Logger.debug("Lock count: #{count}") + end + + praefect_manager.stop_praefect + praefect_manager.create_stalled_replication_queue + + praefect_manager.start_praefect + praefect_manager.wait_for_reliable_connection + + # Create a new project, push to it, and check that replication occurs + project_push = Resource::Repository::ProjectPush.fabricate! do |push| + push.project_name = "gitaly_cluster" + end + + expect(praefect_manager.replicated?(project_push.project.id)).to be true + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb index 3ad56e21ad4..e66e8f8c9d4 100644 --- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -5,33 +5,36 @@ require 'securerandom' require 'digest' module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Compare archives of different user projects with the same name and check they\'re different' do include Support::Api + let(:project_name) { "project-archive-download-#{SecureRandom.hex(8)}" } - before do - @project_name = "project-archive-download-#{SecureRandom.hex(8)}" - @archive_types = %w(tar.gz tar.bz2 tar zip) - @users = { + let(:archive_types) { %w(tar.gz tar.bz2 tar zip) } + + let(:users) do + { user1: { username: Runtime::Env.gitlab_qa_username_1, password: Runtime::Env.gitlab_qa_password_1 }, user2: { username: Runtime::Env.gitlab_qa_username_2, password: Runtime::Env.gitlab_qa_password_2 } } + end - @users.each do |_, user_info| + before do + users.each do |_, user_info| user_info[:user] = Resource::User.fabricate_or_use(user_info[:username], user_info[:password]) user_info[:api_client] = Runtime::API::Client.new(:gitlab, user: user_info[:user]) user_info[:api_client].personal_access_token - user_info[:project] = create_project(user_info[:user], user_info[:api_client], @project_name) + user_info[:project] = create_project(user_info[:user], user_info[:api_client], project_name) end end it 'download archives of each user project then check they are different' do archive_checksums = {} - @users.each do |user_key, user_info| + users.each do |user_key, user_info| archive_checksums[user_key] = {} - @archive_types.each do |type| + archive_types.each do |type| archive_path = download_project_archive_via_api(user_info[:api_client], user_info[:project], type).path archive_checksums[user_key][type] = Digest::MD5.hexdigest(File.read(archive_path)) end diff --git a/qa/qa/specs/features/api/4_verify/.gitkeep b/qa/qa/specs/features/api/4_verify/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 --- a/qa/qa/specs/features/api/4_verify/.gitkeep +++ /dev/null diff --git a/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb new file mode 100644 index 00000000000..a406fa409d5 --- /dev/null +++ b/qa/qa/specs/features/api/4_verify/pipeline_deletion_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify' do + include Support::Api + + let(:api_client) { Runtime::API::Client.new(:gitlab) } + + describe 'Pipeline', :runner do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-pipeline' + end + end + + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.project = project + runner.name = project.name + runner.tags = [project.name] + end + end + + let!(:ci_file) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: <<~YAML + job1: + tags: + - #{project.name} + script: echo 'OK' + YAML + } + ] + ) + end + end + + let!(:pipeline_id) do + pipeline_create_request = Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipeline?ref=master") + JSON.parse(post(pipeline_create_request.url, nil))['id'] + end + + let(:pipeline_data_request) { Runtime::API::Request.new(api_client, "/projects/#{project.id}/pipelines/#{pipeline_id}") } + + after do + runner.remove_via_api! + end + + context 'when deleted via API' do + it 'is not found' do + delete(pipeline_data_request.url) + expect(JSON.parse(get(pipeline_data_request.url))['message'].downcase).to have_content('404 not found') + end + end + end + end +end diff --git a/qa/qa/specs/features/api/1_manage/.gitkeep b/qa/qa/specs/features/api/8_monitor/.gitkeep index e69de29bb2d..e69de29bb2d 100644 --- a/qa/qa/specs/features/api/1_manage/.gitkeep +++ b/qa/qa/specs/features/api/8_monitor/.gitkeep diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb index 7143cc574b8..5b89bcc7375 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Configure', :orchestrated, :mattermost do + RSpec.describe 'Configure', :orchestrated, :mattermost do describe 'Mattermost support' do it 'user creates a group with a mattermost team' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb index 9eab03323a8..f307d286587 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb @@ -1,24 +1,32 @@ # frozen_string_literal: true module QA - context 'Manage' do + RSpec.describe 'Manage' do describe 'Project transfer between groups' do - it 'user transfers a project between groups' do - Flow::Login.sign_in - - source_group = Resource::Group.fabricate_via_api! do |group| + let(:source_group) do + Resource::Group.fabricate_via_api! do |group| group.path = 'source-group' end + end - target_group = Resource::Group.fabricate_via_api! do |group| + let(:target_group) do + Resource::Group.fabricate_via_api! do |group| group.path = 'target-group' end + end - project = Resource::Project.fabricate_via_api! do |project| + let(:project) do + Resource::Project.fabricate_via_api! do |project| project.group = source_group project.name = 'transfer-project' project.initialize_with_readme = true end + end + + let(:edited_readme_content) { 'Here is the edited content.' } + + before do + Flow::Login.sign_in project.visit! @@ -28,14 +36,14 @@ module QA Page::File::Show.perform(&:click_edit) - edited_readme_content = 'Here is the edited content.' - Page::File::Edit.perform do |file| file.remove_content file.add_content(edited_readme_content) file.commit_changes end + end + it 'user transfers a project between groups' do Page::File::Show.perform(&:go_to_general_settings) Page::Project::Settings::Main.perform(&:expand_advanced_settings) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb index 1050005a231..9cb765705e0 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :smoke do + RSpec.describe 'Manage', :smoke do describe 'basic user login' do it 'user logs in using basic credentials and logs out' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb new file mode 100644 index 00000000000..d0ab945124b --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_with_2fa_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module QA + context 'Manage', :requires_admin, :skip_live_env do + describe '2FA' do + let(:owner_user) do + Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_2fa_owner_username_1, Runtime::Env.gitlab_qa_2fa_owner_password_1) + end + + let(:sandbox_group) do + Resource::Sandbox.fabricate! do |sandbox_group| + sandbox_group.path = "gitlab-qa-2fa-sandbox-group" + sandbox_group.api_client = owner_api_client + end + end + + let(:group) do + QA::Resource::Group.fabricate_via_api! do |group| + group.sandbox = sandbox_group + group.api_client = owner_api_client + group.name = 'group-with-2fa' + end + end + + let(:developer_user) do + Resource::User.fabricate_via_api! do |resource| + resource.api_client = admin_api_client + end + end + + let(:two_fa_expected_text) { /The group settings for.*require you to enable Two-Factor Authentication for your account.*You need to do this before/ } + + before do + group.add_member(developer_user, Resource::Members::AccessLevel::DEVELOPER) + end + + it 'allows enforcing 2FA via UI and logging in with 2FA' do + enforce_two_factor_authentication_on_group(group) + + enable_two_factor_authentication_for_user(developer_user) + + Flow::Login.sign_in(as: developer_user, skip_page_validation: true) + + Page::Main::TwoFactorAuth.perform do |two_fa_auth| + two_fa_auth.set_2fa_code('000000') + two_fa_auth.click_verify_code_button + end + + expect(page).to have_text('Invalid two-factor code') + + Page::Main::TwoFactorAuth.perform do |two_fa_auth| + two_fa_auth.set_2fa_code(@otp.fresh_otp) + two_fa_auth.click_verify_code_button + end + + expect(Page::Main::Menu.perform(&:signed_in?)).to be_truthy + end + + after do + group.set_require_two_factor_authentication(value: 'false') + group.remove_via_api! do |resource| + resource.api_client = admin_api_client + end + developer_user.remove_via_api! + end + + def admin_api_client + @admin_api_client ||= Runtime::API::Client.as_admin + end + + def owner_api_client + @owner_api_client ||= Runtime::API::Client.new(:gitlab, user: owner_user) + end + + # We are intentionally using the UI to enforce 2FA to exercise the flow with UI. + # Any future tests should use the API for this purpose. + def enforce_two_factor_authentication_on_group(group) + Flow::Login.while_signed_in(as: owner_user) do + group.visit! + + Page::Group::Menu.perform(&:click_group_general_settings_item) + Page::Group::Settings::General.perform(&:set_require_2fa_enabled) + + expect(page).to have_text(two_fa_expected_text) + + Page::Profile::TwoFactorAuth.perform(&:click_configure_it_later_button) + + expect(page).not_to have_text(two_fa_expected_text) + end + end + + def enable_two_factor_authentication_for_user(user) + Flow::Login.while_signed_in(as: user) do + expect(page).to have_text(two_fa_expected_text) + + Page::Profile::TwoFactorAuth.perform do |two_fa_auth| + @otp = QA::Support::OTP.new(two_fa_auth.otp_secret_content) + + two_fa_auth.set_pin_code(@otp.fresh_otp) + two_fa_auth.click_register_2fa_app_button + + expect(two_fa_auth).to have_text('Congratulations! You have enabled Two-factor Authentication!') + + two_fa_auth.click_proceed_button + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb index 46a0f1a4c8b..5933637045f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do + RSpec.describe 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do describe 'LDAP login' do it 'user logs into GitLab using LDAP credentials' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb index a680cfa96bd..c7bd372c144 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :orchestrated, :mattermost do + RSpec.describe 'Manage', :orchestrated, :mattermost do describe 'Mattermost login' do it 'user logs into Mattermost using GitLab OAuth' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb index ad67f02eaca..505da623d66 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :orchestrated, :instance_saml do + RSpec.describe 'Manage', :orchestrated, :instance_saml do describe 'Instance wide SAML SSO' do it 'User logs in to gitlab with SAML SSO' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index a0e3fe0d91a..9dfeec37869 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - shared_examples 'registration and login' do + RSpec.shared_examples 'registration and login' do it 'user registers and logs in' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,13 +13,13 @@ module QA end end - context 'Manage', :skip_signup_disabled do + RSpec.describe 'Manage', :skip_signup_disabled do describe 'standard' do it_behaves_like 'registration and login' end end - context 'Manage', :orchestrated, :ldap_no_tls, :skip_signup_disabled do + RSpec.describe 'Manage', :orchestrated, :ldap_no_tls, :skip_signup_disabled do describe 'while LDAP is enabled' do it_behaves_like 'registration and login' end 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 67055537567..8d1fa3ee62d 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage' do + RSpec.describe 'Manage' do describe 'Add project member' do it 'user adds project member' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb index 9ca933a957f..80b5e332abe 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :smoke do + RSpec.describe 'Manage', :smoke do describe 'Project creation' do it 'user creates a new project' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb index 1f1adf9afb4..6ce97188d66 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb @@ -3,7 +3,7 @@ require 'nokogiri' module QA - context 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do + RSpec.describe 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do describe 'Check for broken images', :requires_admin do before(:context) do admin = QA::Resource::User.new.tap do |user| 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 409d67d51b1..94cf1fe1f8c 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,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage', :github, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/26952', type: :bug } do + RSpec.describe '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| @@ -62,12 +62,9 @@ module QA Page::Project::Issue::Show.perform do |issue_page| expect(issue_page).to have_comment(comment_text) - end - - Page::Issuable::Sidebar.perform do |issuable| - expect(issuable).to have_label('enhancement') - expect(issuable).to have_label('help wanted') - expect(issuable).to have_label('good first issue') + expect(issue_page).to have_label('enhancement') + expect(issue_page).to have_label('help wanted') + expect(issue_page).to have_label('good first issue') end end end @@ -91,9 +88,9 @@ module QA expect(page).to have_content('[Review comment] Nice blank line.') expect(page).to have_content('[Single diff comment] Much better without this line!') - Page::Issuable::Sidebar.perform do |issuable| - expect(issuable).to have_label('bug') - expect(issuable).to have_label('enhancement') + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_label('bug') + expect(merge_request).to have_label('enhancement') end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb new file mode 100644 index 00000000000..e40dde64675 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage' do + describe 'Repository tags' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-for-tags' + project.initialize_with_readme = true + end + end + + let(:developer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } + let(:maintainer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) } + let(:tag_name) { 'v0.0.1' } + let(:tag_message) { 'Version 0.0.1' } + let(:tag_release_notes) { 'Release It!' } + + shared_examples 'successful tag creation' do |user| + it "can be created by #{user}" do + Flow::Login.sign_in(as: send(user)) + + create_tag_for_project(project, tag_name, tag_message, tag_release_notes) + + Page::Project::Tag::Show.perform do |show| + expect(show).to have_tag_name(tag_name) + expect(show).to have_tag_message(tag_message) + expect(show).to have_tag_release_notes(tag_release_notes) + expect(show).not_to have_element(:create_tag_button) + end + end + end + + shared_examples 'unsuccessful tag creation' do |user| + it "cannot be created by an unauthorized #{user}" do + Flow::Login.sign_in(as: send(user)) + + create_tag_for_project(project, tag_name, tag_message, tag_release_notes) + + Page::Project::Tag::New.perform do |new_tag| + expect(new_tag).to have_content('You are not allowed to create this tag as it is protected.') + expect(new_tag).to have_element(:create_tag_button) + end + end + end + + context 'when not protected' do + before do + add_members_to_project(project) + end + + it_behaves_like 'successful tag creation', :developer_user + it_behaves_like 'successful tag creation', :maintainer_user + end + + context 'when protected' do + before do + add_members_to_project(project) + + Flow::Login.sign_in + + protect_tag_for_project(project, 'v*', 'Maintainers') + + Page::Main::Menu.perform(&:sign_out) + end + + it_behaves_like 'unsuccessful tag creation', :developer_user + it_behaves_like 'successful tag creation', :maintainer_user + end + + def create_tag_for_project(project, name, message, release_notes) + project.visit! + + Page::Project::Menu.perform(&:go_to_repository_tags) + Page::Project::Tag::Index.perform(&:click_new_tag_button) + + Page::Project::Tag::New.perform do |new_tag| + new_tag.fill_tag_name(name) + new_tag.fill_tag_message(message) + new_tag.fill_release_notes(release_notes) + new_tag.click_create_tag_button + end + end + + def protect_tag_for_project(project, tag, role) + project.visit! + + Page::Project::Menu.perform(&:go_to_repository_settings) + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_protected_tags do |protected_tags| + protected_tags.set_tag(tag) + protected_tags.choose_access_level_role(role) + + protected_tags.click_protect_tag_button + end + end + end + + def add_members_to_project(project) + @developer_user = developer_user + @maintainer_user = maintainer_user + + project.add_member(@developer_user, Resource::Members::AccessLevel::DEVELOPER) + project.add_member(@maintainer_user, Resource::Members::AccessLevel::MAINTAINER) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index d483dcc97a7..b98d2982684 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Manage' do + RSpec.describe 'Manage' do describe 'Project activity' do it 'user creates an event in the activity page upon Git push' do Flow::Login.sign_in 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 0a577aa07f8..3717bc8a9ff 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan', :orchestrated, :smtp do + RSpec.describe 'Plan', :orchestrated, :smtp do describe 'Email Notification' do include Support::Api 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 57d2c02a27b..784f474a7d5 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,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan', :reliable do + RSpec.describe 'Plan', :reliable do describe 'check xss occurence in @mentions in issues', :requires_admin do it 'mentions a user in a comment' do QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token 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 33d2c7026b3..478f6b8177c 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', :reliable do + RSpec.describe 'Plan', :reliable do describe 'collapse comments in issue discussions' do let(:my_first_reply) { 'My first 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 4667eb6c587..0347de42b96 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', :reliable do + RSpec.describe 'Plan', :reliable do describe 'Issue comments' do before do Flow::Login.sign_in 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 57b0859856e..e41024e5d14 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan', :smoke do + RSpec.describe 'Plan', :smoke do describe 'Issue creation' do let(:closed_issue) { Resource::Issue.fabricate_via_api! } diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb index 3727aae2270..aa03a514f04 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb @@ -3,7 +3,7 @@ require 'securerandom' module QA - context 'Plan', :reliable do + RSpec.describe 'Plan', :reliable do describe 'Issues list' do let(:project) do Resource::Project.fabricate_via_api! do |project| 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 b7687f785a8..082933e9878 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', :reliable do + RSpec.describe 'Plan', :reliable do describe 'filter issue comments activities' do before do Flow::Login.sign_in 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 623573a1397..43f4415c90d 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', :reliable do + RSpec.describe 'Plan', :reliable do describe 'issue suggestions' do let(:issue_title) { 'Issue Lists are awesome' } diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb new file mode 100644 index 00000000000..a2e7d10f313 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Plan' do + describe 'Jira issue import', :jira, :orchestrated, :requires_admin do + let(:jira_project_key) { "JITD" } + let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" } + let(:jira_issue_description) { "This issue is for testing importing Jira issues to GitLab." } + let(:jira_issue_label_1) { "jira-import::#{jira_project_key}-1" } + let(:jira_issue_label_2) { "QA" } + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = "jira_issue_import" + end + end + + it 'imports issues from Jira' do + set_up_jira_integration + import_jira_issues + + QA::Support::Retrier.retry_on_exception do + Page::Project::Menu.perform(&:click_issues) + + Page::Project::Issue::Index.perform do |issues_page| + expect(issues_page).to have_content("2 issues successfully imported") + + issues_page.click_issue_link(jira_issue_title) + end + end + + expect(page).to have_content(jira_issue_description) + + Page::Project::Issue::Show.perform do |issue| + expect(issue).to have_label(jira_issue_label_1) + expect(issue).to have_label(jira_issue_label_2) + end + end + + private + + def set_up_jira_integration + # Retry is required because allow_local_requests_from_web_hooks_and_services + # takes some time to get enabled. + # Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010 + QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do + Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true) + + page.visit Runtime::Scenario.gitlab_address + Flow::Login.sign_in_unless_signed_in + + project.visit! + + Page::Project::Menu.perform(&:go_to_integrations_settings) + QA::Page::Project::Settings::Integrations.perform(&:click_jira_link) + + QA::Page::Project::Settings::Services::Jira.perform do |jira| + jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url)) + end + + expect(page).not_to have_text("Url is blocked") + expect(page).to have_text("Jira activated") + end + end + + def import_jira_issues + Page::Project::Menu.perform(&:click_issues) + Page::Project::Issue::Index.perform(&:go_to_jira_import_form) + + Page::Project::Issue::JiraImport.perform do |form| + form.select_project_and_import(jira_project_key) + end + + expect(page).to have_content("Import in progress") + end + end + end +end 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 3e575517ecb..50df1c3ef01 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,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan', :smoke, :reliable do + RSpec.describe 'Plan', :smoke, :reliable do describe 'mention' do before do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb index c81a6f9281c..932eef8e38b 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Plan', :reliable do + RSpec.describe 'Plan', :reliable do describe 'Issue board focus mode' do let(:project) do QA::Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb new file mode 100644 index 00000000000..115701c5c02 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Plan' do + describe 'Milestones' do + include Support::Dates + + let(:start_date) { current_date_yyyy_mm_dd } + let(:due_date) { next_month_yyyy_mm_dd } + + let(:group) do + Resource::Group.fabricate_via_api! do |group| + group.name = 'group-to-test-milestones' + end + end + + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-to-test-milestones' + end + end + + let(:issue) do + Resource::Issue.fabricate_via_api! do |issue| + issue.project = project + end + end + + before do + Flow::Login.sign_in + end + + shared_examples 'milestone assigned to existing issue' do + it 'is assigned to an existing issue' do + issue.visit! + + Page::Project::Issue::Show.perform do |existing_issue| + existing_issue.assign_milestone(milestone) + + expect(existing_issue).to have_milestone(milestone.title) + end + end + end + + shared_examples 'milestone assigned to new issue' do + it 'is assigned to a new issue' do + Resource::Issue.fabricate_via_browser_ui! do |new_issue| + new_issue.project = project + new_issue.milestone = milestone + end + + Page::Project::Issue::Show.perform do |issue| + expect(issue).to have_milestone(milestone.title) + end + end + end + + context 'Group milestone' do + let(:milestone) do + Resource::GroupMilestone.fabricate_via_api! do |milestone| + milestone.group = group + milestone.start_date = start_date + milestone.due_date = due_date + end + end + + it_behaves_like 'milestone assigned to existing issue' + it_behaves_like 'milestone assigned to new issue' + end + + context 'Project milestone' do + let(:milestone) do + Resource::ProjectMilestone.fabricate_via_api! do |milestone| + milestone.project = project + milestone.start_date = start_date + milestone.due_date = due_date + end + end + + it_behaves_like 'milestone assigned to existing issue' + it_behaves_like 'milestone assigned to new issue' + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb new file mode 100644 index 00000000000..35c42796aeb --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Plan' do + describe 'Group milestone' do + include Support::Dates + + let(:title) { 'Group milestone' } + let(:description) { 'This milestone tests out group milestones.' } + let(:start_date) { current_date_yyyy_mm_dd } + let(:due_date) { next_month_yyyy_mm_dd } + + before do + Flow::Login.sign_in + end + + it 'creates a group milestone' do + group_milestone = Resource::GroupMilestone.fabricate_via_browser_ui! do |milestone| + milestone.title = title + milestone.description = description + milestone.start_date = start_date + milestone.due_date = due_date + end + + Page::Group::Menu.perform(&:go_to_milestones) + Page::Group::Milestone::Index.perform do |milestone_list| + expect(milestone_list).to have_milestone(group_milestone) + + milestone_list.click_milestone(group_milestone) + end + + Page::Milestone::Show.perform do |milestone| + expect(milestone).to have_element(:milestone_title_content, text: title) + expect(milestone).to have_element(:milestone_description_content, text: description) + expect(milestone).to have_start_date(start_date) + expect(milestone).to have_due_date(due_date) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb new file mode 100644 index 00000000000..143fdf5728b --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Plan' do + describe 'Project milestone' do + include Support::Dates + + let(:title) { 'Project milestone' } + let(:description) { 'This issue tests out project milestones.' } + let(:start_date) { current_date_yyyy_mm_dd } + let(:due_date) { next_month_yyyy_mm_dd } + + before do + Flow::Login.sign_in + end + + it 'creates a project milestone' do + project_milestone = Resource::ProjectMilestone.fabricate_via_browser_ui! do |milestone| + milestone.title = title + milestone.description = description + milestone.start_date = start_date + milestone.due_date = due_date + end + + Page::Project::Menu.perform(&:go_to_milestones) + Page::Project::Milestone::Index.perform do |milestone_list| + expect(milestone_list).to have_milestone(project_milestone) + + milestone_list.click_milestone(project_milestone) + end + + Page::Milestone::Show.perform do |milestone| + expect(milestone).to have_element(:milestone_title_content, text: title) + expect(milestone).to have_element(:milestone_description_content, text: description) + expect(milestone).to have_start_date(start_date) + expect(milestone).to have_due_date(due_date) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb index 3bb03f68d51..97a76c1aa01 100644 --- a/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do context 'Gitaly' do describe 'High Availability', :orchestrated, :gitaly_ha do let(:project) do @@ -10,15 +10,15 @@ module QA end end let(:initial_file) { 'pushed_to_primary.txt' } - let(:final_file) { 'pushed_to_secondary.txt' } + let(:final_file) { 'committed_to_primary.txt' } + let(:praefect_manager) { Service::PraefectManager.new } before do - @praefect_manager = Service::PraefectManager.new Flow::Login.sign_in end after do - @praefect_manager.reset + praefect_manager.reset_cluster end it 'makes sure that automatic failover is happening' do @@ -30,7 +30,7 @@ module QA push.file_content = "This should exist on both nodes" end - @praefect_manager.stop_primary_node + praefect_manager.trigger_failover_by_stopping_primary_node project.visit! @@ -41,11 +41,13 @@ module QA expect(show).to have_file(initial_file) end + praefect_manager.enable_writes + Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project commit.add_files([ { - file_path: 'committed_to_primary.txt', + file_path: final_file, content: 'This should exist on both nodes too' } ]) diff --git a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb index 05a932fd53e..28338475cb5 100644 --- a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb @@ -1,23 +1,24 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do include Support::Api describe 'Jira integration', :jira, :orchestrated, :requires_admin do let(:jira_project_key) { 'JITP' } + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = "project_with_jira_integration" + end + end - before(:all) do + before do page.visit Vendor::Jira::JiraAPI.perform(&:base_url) QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, raise_on_failure: true) do page.has_text? 'Welcome to Jira' end - @project = Resource::Project.fabricate_via_api! do |project| - project.name = "project_with_jira_integration" - end - # Retry is required because allow_local_requests_from_web_hooks_and_services # takes some time to get enabled. # Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010 @@ -27,7 +28,7 @@ module QA page.visit Runtime::Scenario.gitlab_address Flow::Login.sign_in_unless_signed_in - @project.visit! + project.visit! Page::Project::Menu.perform(&:go_to_integrations_settings) QA::Page::Project::Settings::Integrations.perform(&:click_jira_link) @@ -67,9 +68,11 @@ module QA expect_issue_done(issue_key) end + private + def create_mr_with_description(description) Resource::MergeRequest.fabricate! do |merge_request| - merge_request.project = @project + merge_request.project = project merge_request.target_new_branch = !master_branch_exists? merge_request.description = description end @@ -80,7 +83,7 @@ module QA push.branch_name = 'master' push.commit_message = commit_message push.file_content = commit_message - push.project = @project + push.project = project push.new_branch = !master_branch_exists? end end @@ -98,7 +101,7 @@ module QA end def master_branch_exists? - @project.repository_branches.map { |item| item[:name] }.include?("master") + project.repository_branches.map { |item| item[:name] }.include?("master") end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 4a9901f2a84..a002779d7d9 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 @@ -1,29 +1,31 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Create a new merge request' do - before do - Flow::Login.sign_in - - @project = Resource::Project.fabricate_via_api! do |project| + let(:project) do + Resource::Project.fabricate_via_api! do |project| project.name = 'project' end + end + + let(:merge_request_title) { 'One merge request to rule them all' } + let(:merge_request_description) { '... to find them, to bring them all, and in the darkness bind them' } - @merge_request_title = 'One merge request to rule them all' - @merge_request_description = '... to find them, to bring them all, and in the darkness bind them' + before do + Flow::Login.sign_in end 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 - merge_request.description = @merge_request_description + merge_request.project = project + merge_request.title = merge_request_title + merge_request.description = merge_request_description end Page::MergeRequest::Show.perform do |merge_request| - expect(merge_request).to have_title(@merge_request_title) - expect(merge_request).to have_description(@merge_request_description) + expect(merge_request).to have_title(merge_request_title) + expect(merge_request).to have_description(merge_request_description) end end @@ -31,32 +33,29 @@ module QA gitlab_account_username = "@#{Runtime::User.username}" milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone| - milestone.project = @project + milestone.project = project end label = Resource::Label.fabricate_via_api! do |label| - label.project = @project + label.project = project label.title = 'label' end Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| - merge_request.title = @merge_request_title - merge_request.description = @merge_request_description - merge_request.project = @project + merge_request.title = merge_request_title + merge_request.description = merge_request_description + merge_request.project = project merge_request.milestone = milestone merge_request.assignee = 'me' merge_request.labels.push(label) end Page::MergeRequest::Show.perform do |merge_request| - expect(merge_request).to have_title(@merge_request_title) - expect(merge_request).to have_description(@merge_request_description) + expect(merge_request).to have_title(merge_request_title) + expect(merge_request).to have_description(merge_request_description) expect(merge_request).to have_assignee(gitlab_account_username) expect(merge_request).to have_label(label.title) - end - - Page::Issuable::Sidebar.perform do |sidebar| - expect(sidebar).to have_milestone(milestone.title) + expect(merge_request).to have_milestone(milestone.title) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 3964ae7eada..5b89bf046fb 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 @@ -1,19 +1,16 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Merge request creation from fork' do - it 'user forks a project, submits a merge request and maintainer merges it' do - Flow::Login.sign_in - - merge_request = Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request| + let(:merge_request) do + Resource::MergeRequestFromFork.fabricate_via_api! do |merge_request| merge_request.fork_branch = 'feature-branch' end + end - merge_request.project.api_put(auto_devops_enabled: false) - - Page::Main::Menu.perform(&:sign_out) - Page::Main::Login.perform(&:sign_in_using_credentials) + it 'can merge feature branch fork to mainline' do + Flow::Login.sign_in merge_request.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 4e189faec6e..a1c604bdcfc 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,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/30226', type: :bug } do + RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/30226', 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/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index 9236609934e..cb660a3e40b 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -1,19 +1,23 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Merge request squashing' do - it 'user squashes commits while merging' do - Flow::Login.sign_in - - project = Resource::Project.fabricate_via_api! do |project| + let(:project) do + Resource::Project.fabricate_via_api! do |project| project.name = "squash-before-merge" end + end - merge_request = Resource::MergeRequest.fabricate! do |merge_request| + let(:merge_request) do + Resource::MergeRequest.fabricate_via_api! do |merge_request| merge_request.project = project merge_request.title = 'Squashing commits' end + end + + before do + Flow::Login.sign_in Resource::Repository::ProjectPush.fabricate! do |push| push.project = project @@ -25,7 +29,9 @@ module QA end merge_request.visit! + end + it 'user squashes commits while merging' do Page::MergeRequest::Show.perform do |merge_request_page| merge_request_page.retry_on_exception(reload: true) do expect(merge_request_page).to have_text('to be squashed') @@ -34,13 +40,9 @@ module QA merge_request_page.mark_to_squash merge_request_page.merge! - merge_request.project.visit! - Git::Repository.perform do |repository| - repository.uri = merge_request.project.repository_http_location.uri - + repository.uri = project.repository_http_location.uri repository.use_default_credentials - repository.clone expect(repository.commits.size).to eq 3 diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb index 604b6c10aee..3c2c068dfd1 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Download merge request patch and diff' do before(:context) do @merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index d5346546efe..c02632c2c60 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true +require 'securerandom' + module QA - context 'Create' do + RSpec.describe 'Create' do describe 'File templates' do include Runtime::Fixtures - before(:all) do - @project = Resource::Project.fabricate_via_api! do |project| + let(:project) do + Resource::Project.fabricate_via_api! do |project| project.name = 'file-template-project' project.description = 'Add file templates via the Files view' project.initialize_with_readme = true @@ -46,7 +48,7 @@ module QA Flow::Login.sign_in - @project.visit! + project.visit! Page::Project::Show.perform(&:create_new_file!) Page::File::Form.perform do |form| @@ -54,12 +56,14 @@ module QA expect(form).to have_normalized_ws_text(content[0..100]) + form.add_name("#{SecureRandom.hex(8)}/#{template[:file_name]}") form.commit_changes - expect(form).to have_content('The file has been successfully created.') - expect(form).to have_content(template[:file_name]) - expect(form).to have_content('Add new file') - expect(form).to have_normalized_ws_text(content[0..100]) + aggregate_failures "indications of file created" do + expect(form).to have_content(template[:file_name]) + expect(form).to have_normalized_ws_text(content[0..100]) + expect(form).to have_content('Add new file') + end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index bf5a9501cba..cab909756c1 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Create, list, and delete branches via web' do master_branch = 'master' second_branch = 'second-branch' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb deleted file mode 100644 index 68bbc1719fc..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module QA - context 'Create' do - describe 'SSH keys support' do - let(:key_title) { "key for ssh tests #{Time.now.to_f}" } - - it 'user adds and then removes an SSH key', :smoke do - Flow::Login.sign_in - - key = Resource::SSHKey.fabricate_via_browser_ui! do |resource| - resource.title = key_title - end - - expect(page).to have_content(key.title) - expect(page).to have_content(key.md5_fingerprint) - - Page::Main::Menu.perform(&:click_settings_link) - Page::Profile::Menu.perform(&:click_ssh_keys) - - Page::Profile::SSHKeys.perform do |ssh_keys| - ssh_keys.remove_key(key_title) - end - - expect(page).not_to have_content("Title: #{key_title}") - expect(page).not_to have_content(key.md5_fingerprint) - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb index 0650c8395c7..c9cbc68c254 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb @@ -1,16 +1,18 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Git clone over HTTP', :ldap_no_tls do - before(:all) do - @project = Resource::Project.fabricate_via_api! do |scenario| + let(:project) do + Resource::Project.fabricate_via_api! do |scenario| scenario.name = 'project-with-code' scenario.description = 'project for git clone tests' end + end + before do Git::Repository.perform do |repository| - repository.uri = @project.repository_http_location.uri + repository.uri = project.repository_http_location.uri repository.use_default_credentials repository.act do @@ -21,12 +23,12 @@ module QA push_changes end end - @project.wait_for_push_new_branch + project.wait_for_push_new_branch end it 'user performs a deep clone' do Git::Repository.perform do |repository| - repository.uri = @project.repository_http_location.uri + repository.uri = project.repository_http_location.uri repository.use_default_credentials repository.clone @@ -37,7 +39,7 @@ module QA it 'user performs a shallow clone' do Git::Repository.perform do |repository| - repository.uri = @project.repository_http_location.uri + repository.uri = project.repository_http_location.uri repository.use_default_credentials repository.shallow_clone diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb index 51a1c19f0f7..d66f0ddcda6 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Files management' do it 'user creates, edits and deletes a file via the Web' do Runtime::Browser.visit(:gitlab, Page::Main::Login) 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 index d0123da53bb..f586c25165c 100644 --- 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do + RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin do let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } let(:parent_project) do 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 b210e747614..e3b0d7de9ec 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' 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 e3d5b755317..90beff343ab 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' 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/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index d8aaffc3713..c01558d3702 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Git push over HTTP', :ldap_no_tls do it 'user using a personal access token pushes code to the repository' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index ae95f5a7a44..254e32a88ce 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Push mirror a repository over HTTP' do it 'configures and syncs a (push) mirrored repository' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 21ae10774c9..443ace0c9f0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create', :requires_admin do + RSpec.describe 'Create', :requires_admin do describe 'push after setting the file size limit via admin/application_settings' do # Note: The file size limits in this test should be greater than the limits in # ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb index 6c0d55cc69a..b918b2ff268 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Git push over HTTP', :ldap_no_tls do it 'user pushes code to the repository' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb index 455db4d811d..3e1e470d8c3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Protected branch support', :ldap_no_tls do let(:branch_name) { 'protected-branch' } let(:commit_message) { 'Protected push commit message' } diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb new file mode 100644 index 00000000000..d67e4a4ea83 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/ssh_key_support_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'SSH keys support', :smoke do + key_title = "key for ssh tests #{Time.now.to_f}" + key = nil + + before do + Flow::Login.sign_in + end + + it 'user can add an SSH key' do + key = Resource::SSHKey.fabricate_via_browser_ui! do |resource| + resource.title = key_title + end + + expect(page).to have_content(key.title) + expect(page).to have_content(key.md5_fingerprint) + end + + # Note this context ensures that the example it contains is executed after the example above. Be aware of the order of execution if you add new examples in either context. + context 'after adding an ssh key' do + it 'can delete an ssh key' do + Page::Main::Menu.perform(&:click_settings_link) + Page::Profile::Menu.perform(&:click_ssh_keys) + Page::Profile::SSHKeys.perform do |ssh_keys| + ssh_keys.remove_key(key_title) + end + + expect(page).not_to have_content("Title: #{key_title}") + expect(page).not_to have_content(key.md5_fingerprint) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 1a3c6d03098..e91717b0f5f 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'SSH key support' do # Note: If you run this test against GDK make sure you've enabled sshd # See: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb index aee62bacfa8..cf91b829817 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Commit data' do before(:context) do # Get the user's details to confirm they're included in the email patch diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb new file mode 100644 index 00000000000..a867d9cb973 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + describe 'Adding comments on snippets' do + let(:comment_author) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } + let(:comment_content) { 'Comment 123' } + let(:edited_comment_content) { 'Nice snippet!' } + + let(:personal_snippet) do + Resource::Snippet.fabricate! do |snippet| + snippet.title = 'Personal snippet with a comment' + end + end + + let(:project_snippet) do + Resource::ProjectSnippet.fabricate! do |snippet| + snippet.title = 'Project snippet with a comment' + end + end + + before do + Flow::Login.sign_in + end + + shared_examples 'comments on snippets' do |snippet_type| + it "adds, edits, and deletes a comment on a #{snippet_type}" do + send(snippet_type) + + Page::Main::Menu.perform(&:sign_out) + + Flow::Login.sign_in(as: comment_author) + + send(snippet_type).visit! + + create_comment + verify_comment_content(comment_author.username, comment_content) + + edit_comment + verify_comment_content(comment_author.username, edited_comment_content) + + delete_comment + verify_comment_deleted + end + end + + it_behaves_like 'comments on snippets', :personal_snippet + it_behaves_like 'comments on snippets', :project_snippet + + def create_comment + Page::Dashboard::Snippet::Show.perform do |snippet| + snippet.add_comment(comment_content) + end + end + + def edit_comment + Page::Dashboard::Snippet::Show.perform do |snippet| + snippet.edit_comment(edited_comment_content) + end + end + + def delete_comment + Page::Dashboard::Snippet::Show.perform do |snippet| + snippet.delete_comment(edited_comment_content) + end + end + + def verify_comment_content(author, comment_content) + Page::Dashboard::Snippet::Show.perform do |comment| + expect(comment).to have_comment_author(author) + expect(comment).to have_comment_content(comment_content) + end + end + + def verify_comment_deleted + expect(page).not_to have_content(comment_author.username) + expect(page).not_to have_content(edited_comment_content) + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb index 341ff39fdf1..e6589851dd9 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Version control for personal snippets' do let(:new_file) { 'new_snippet_file' } let(:changed_content) { 'changes' } diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb index a3011550db8..1660944fccd 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Version control for project snippets' do let(:new_file) { 'new_snippet_file' } let(:changed_content) { 'changes' } diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb index 451a7847f8b..d2b86904cd3 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb @@ -1,27 +1,30 @@ # frozen_string_literal: true module QA - context 'Create', :smoke do + RSpec.describe 'Create', :smoke do describe 'Personal snippet creation' do it 'User creates a personal snippet' do Flow::Login.sign_in - Page::Main::Menu.perform(&:go_to_snippets) + Page::Main::Menu.perform do |menu| + menu.go_to_more_dropdown_option(:snippets_link) + end Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' snippet.description = 'Snippet description' snippet.visibility = 'Private' - snippet.file_name = 'New snippet file name' - snippet.file_content = 'Snippet file text' + snippet.file_name = 'ruby_file.rb' + snippet.file_content = 'File.read("test.txt").split(/\n/)' end Page::Dashboard::Snippet::Show.perform do |snippet| expect(snippet).to have_snippet_title('Snippet title') expect(snippet).to have_snippet_description('Snippet description') expect(snippet).to have_visibility_type(/private/i) - expect(snippet).to have_file_name('New snippet file name') - expect(snippet).to have_file_content('Snippet file text') + expect(snippet).to have_file_name('ruby_file.rb') + expect(snippet).to have_file_content('File.read("test.txt").split(/\n/)') + expect(snippet).to have_syntax_highlighting('ruby') end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb index 8fc4427bda7..05795e9b51e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do # to be converted to a smoke test once proved to be stable + RSpec.describe 'Create' do # to be converted to a smoke test once proved to be stable describe 'Project snippet creation' do it 'User creates a project snippet' do Flow::Login.sign_in 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 7c9db5ee496..1e3cb0e2ffc 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,9 @@ # frozen_string_literal: true +require 'securerandom' + module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Web IDE file templates' do include Runtime::Fixtures @@ -53,10 +55,11 @@ module QA ide.create_new_file_from_template template[:file_name], template[:name] expect(ide.has_file?(template[:file_name])).to be_truthy - expect(ide).to have_button('Undo') expect(ide).to have_normalized_ws_text(content[0..100]) + ide.rename_file(template[:file_name], "#{SecureRandom.hex(8)}/#{template[:file_name]}") + ide.commit_changes expect(ide).to have_content(template[:file_name]) diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb index 3bf6e156967..fbf70153e1d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'First file using Web IDE' do let(:project) do Resource::Project.fabricate_via_api! do |project| diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb index ed988bdf046..7e0d8822101 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do describe 'Review a merge request in Web IDE' do let(:new_file) { 'awesome_new_file.txt' } let(:original_text) { 'Text' } diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb index 1d0c8ee60d4..77a5998362c 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do context 'Wiki' do describe 'testing wiki content creation inside a project' do let(:new_wiki_title) { "just_another_wiki_page" } diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb index 10370c80476..dbc7798a594 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Create' do + RSpec.describe 'Create' do context 'Wiki' do describe 'testing wiki content manipulation inside a project' do let(:new_wiki_title) { "just_another_wiki_page" } diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb new file mode 100644 index 00000000000..d7f59abc361 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_list_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Wiki' do + let(:small_number_of_pages) { 5 } + let(:large_number_of_pages) { 15 } + let(:random_page) { "bulk_#{rand(0..4)}" } + + let(:small_wiki) { create_wiki_pages small_number_of_pages } + let(:large_wiki) { create_wiki_pages large_number_of_pages } + + before do + Flow::Login.sign_in + end + + context 'Sidebar' do + it 'has all expected links that work' do + small_wiki.visit! + + small_number_of_pages.times do |index| + Page::Project::Wiki::Show.perform do |list| + expect(list).to have_page_listed "bulk_#{index}" + end + end + + Page::Project::Wiki::Show.perform do |list| + list.click_page_link random_page + end + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title random_page + end + end + end + + context 'Page List' do + it 'has all expected links that work' do + large_wiki.visit! + + Page::Project::Wiki::Show.perform(&:click_view_all_pages) + + large_number_of_pages.times do |index| + Page::Project::Wiki::List.perform do |list| + expect(list).to have_page_listed "bulk_#{index}" + end + end + + Page::Project::Wiki::List.perform do |list| + list.click_page_link random_page + end + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title random_page + end + end + end + + private + + def create_wiki_pages(no_of_pages) + wiki = Resource::Wiki::ProjectPage.fabricate_via_api! + no_of_pages.times do |index| + Resource::Wiki::ProjectPage.fabricate_via_api! do |page| + page.title = "bulk_#{index}" + page.project = wiki.project + end + end + wiki + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb new file mode 100644 index 00000000000..923c7332748 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_page_deletion_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Create' do + context 'Wiki' do + let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } + + before do + Flow::Login.sign_in + end + + context 'Page deletion' do + it 'has removed the deleted page correctly' do + initial_wiki.visit! + + Page::Project::Wiki::Show.perform(&:click_edit) + Page::Project::Wiki::Edit.perform(&:delete_page) + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_no_page + 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 index f05634bc3c8..41baaa02544 100644 --- 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 @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Verify' do + RSpec.describe 'Verify', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/229724', type: :investigating } do describe 'Add or Remove CI variable via UI', :smoke do let!(:project) do Resource::Project.fabricate_via_api! do |project| 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 2502e8beda7..68b4a38a043 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,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Verify', :docker, :runner do + RSpec.describe 'Verify', :docker, :runner do describe 'Pipeline creation and processing' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let(:max_wait) { 30 } diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index f8a589ad93b..82b15acb664 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Verify', :docker, :runner do + RSpec.describe 'Verify', :docker, :runner do describe 'Runner registration' do let(:executor) { "qa-runner-#{Time.now.to_i}" } let!(:runner) do diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb index 4d549dde858..0436da40982 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Verify', :docker, :runner do + RSpec.describe 'Verify', :docker, :runner do describe 'Code coverage statistics' do let(:simplecov) { '\(\d+.\d+\%\) covered' } let(:executor) { "qa-runner-#{Time.now.to_i}" } diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index 89aba112407..d67fd96d338 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Release' do + RSpec.describe 'Release' do describe 'Deploy key creation' do it 'user adds a deploy key' do Flow::Login.sign_in 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 73f2fef5f1d..18eb52830a2 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,7 +3,7 @@ require 'digest/sha1' module QA - context 'Release', :docker, :runner do + RSpec.describe 'Release', :docker, :runner do describe 'Git clone using a deploy key' do before do Flow::Login.sign_in @@ -20,7 +20,7 @@ module QA resource.project = @project resource.name = @runner_name resource.tags = %w[qa docker] - resource.image = 'gitlab/gitlab-runner:ubuntu' + resource.image = 'gitlab/gitlab-runner:alpine' end end @@ -51,6 +51,7 @@ module QA gitlab_ci = <<~YAML cat-config: script: + - apk add --update --no-cache openssh-client - mkdir -p ~/.ssh - ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts - eval $(ssh-agent -s) diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb index a07f0bd5e54..9bee5c5ee08 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Release', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213222', type: :flaky } do + RSpec.describe 'Release', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/213222', type: :flaky } do describe 'Deploy token creation' do it 'user adds a deploy token' do Flow::Login.sign_in diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb index e1d8c50ab75..673125c90f2 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do + RSpec.describe 'Release', :docker, :runner, :reliable do describe 'Parent-child pipelines dependent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| @@ -29,12 +29,8 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| + expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_passed - - parent_pipeline.retry_on_exception(sleep_interval: 1.0) do - parent_pipeline.click_linked_job(project.name) - end - expect(parent_pipeline).to have_job("child_job") end end @@ -43,12 +39,8 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| + expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_failed - - parent_pipeline.retry_on_exception(sleep_interval: 1.0) do - parent_pipeline.click_linked_job(project.name) - end - expect(parent_pipeline).to have_job("child_job") end end diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb index c365e084991..05b9859f112 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do + RSpec.describe 'Release', :docker, :runner, :reliable do describe 'Parent-child pipelines independent relationship' do let!(:project) do Resource::Project.fabricate_via_api! do |project| @@ -29,12 +29,8 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| + expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_passed - - parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do - parent_pipeline.click_linked_job(project.name) - end - expect(parent_pipeline).to have_job("child_job") end end @@ -43,12 +39,8 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| + expect(parent_pipeline).to have_child_pipeline expect(parent_pipeline).to have_passed - - parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do - parent_pipeline.click_linked_job(project.name) - end - expect(parent_pipeline).to have_job("child_job") 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 292fc40bec4..ad87ee173f5 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 @@ -3,7 +3,7 @@ require 'pathname' module QA - context 'Configure' do + RSpec.describe 'Configure' do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = Runtime::Env.auto_devops_project_name || 'autodevops-project' diff --git a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb index 0e9c369e97f..5073b715341 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module QA - context 'Configure' do - describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env do + RSpec.describe 'Configure' do + describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/225315', type: :flaky } do context 'Project Clusters' do let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! } let(:project) do diff --git a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb index a9ed6651069..9cfdc4277a7 100644 --- a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb +++ b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - context 'Monitor' do + RSpec.describe 'Monitor' do describe 'with Prometheus in a Gitlab-managed cluster', :orchestrated, :kubernetes do before :all do @cluster = Service::KubernetesCluster.new.create! @@ -64,6 +64,19 @@ module QA end end + it 'observes cluster health graph' do + Page::Project::Menu.perform(&:go_to_operations_kubernetes) + + Page::Project::Operations::Kubernetes::Index.perform do |cluster_list| + cluster_list.click_on_cluster(@cluster) + end + + Page::Project::Operations::Kubernetes::Show.perform do |cluster_panel| + cluster_panel.open_health + cluster_panel.wait_for_cluster_health + end + end + private def deploy_project_with_prometheus 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 c6d5fba919b..73bb6aeb5fd 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 @@ -1,8 +1,8 @@ # frozen_string_literal: true module QA - context 'Non-devops' do - describe 'Performance bar display', :requires_admin do + RSpec.describe 'Non-devops' do + describe 'Performance bar display', :requires_admin, :skip_live_env do context 'when logged in as an admin user' do # performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided) let(:minimum_metrics_count) { 3 } diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb index dd3a50ac128..6a3becf0ee5 100644 --- a/qa/qa/specs/helpers/quarantine.rb +++ b/qa/qa/specs/helpers/quarantine.rb @@ -18,6 +18,10 @@ module QA config.before do |example| Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example) + + if example.metadata.key?(:only) + skip('Test is not compatible with this environment') unless Runtime::Env.address_matches?(example.metadata[:only]) + end end end end @@ -46,21 +50,15 @@ module QA skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters) else if example.metadata.key?(:quarantine) - quarantine_message = %w(In quarantine) quarantine_tag = example.metadata[:quarantine] - if !!quarantine_tag - quarantine_message << case quarantine_tag - when String - ": #{quarantine_tag}" - when Hash - ": #{quarantine_tag[:issue]}" - else - '' - end + if quarantine_tag&.is_a?(Hash) && quarantine_tag&.key?(:only) + # If the :quarantine hash contains :only, we respect that. + # For instance `quarantine: { only: { subdomain: :staging } }` will only quarantine the test when it runs against staging. + return unless Runtime::Env.address_matches?(quarantine_tag[:only]) end - skip(quarantine_message.join(' ').strip) + skip(quarantine_message(quarantine_tag)) end end end @@ -69,6 +67,20 @@ module QA filter.reject { |key, _| key == :quarantine } end + def quarantine_message(quarantine_tag) + quarantine_message = %w(In quarantine) + quarantine_message << case quarantine_tag + when String + ": #{quarantine_tag}" + when Hash + quarantine_tag.key?(:issue) ? ": #{quarantine_tag[:issue]}" : '' + else + '' + end + + quarantine_message.join(' ').strip + end + # Checks if a test or context should be skipped. # # Returns true if diff --git a/qa/qa/support/dates.rb b/qa/qa/support/dates.rb index 47fc721afc1..3d1f146730b 100644 --- a/qa/qa/support/dates.rb +++ b/qa/qa/support/dates.rb @@ -11,6 +11,11 @@ module QA current_date.next_month.strftime("%Y/%m/%d") end + def format_date(date) + new_date = DateTime.strptime(date, "%Y/%m/%d") + new_date.strftime("%b %-d, %Y") + end + private def current_date diff --git a/qa/qa/support/otp.rb b/qa/qa/support/otp.rb new file mode 100644 index 00000000000..0d7c394cf69 --- /dev/null +++ b/qa/qa/support/otp.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require 'rotp' + +module QA + module Support + class OTP + def initialize(secret) + @rotp = ROTP::TOTP.new(secret) + end + + def fresh_otp + otps = [] + + # Fetches a fresh OTP and returns it only after rotp provides the same OTP twice + # An OTP is valid for 30 seconds so 70 attempts with 0.5 interval would ensure we complete 1 cycle + Support::Retrier.retry_until(max_attempts: 70, sleep_interval: 0.5) do + otps << @rotp.now + otps.size >= 3 && otps[-1] == otps[-2] && otps[-1] != otps[-3] + end + + otps.last + end + end + end +end diff --git a/qa/spec/runtime/api/request_spec.rb b/qa/spec/runtime/api/request_spec.rb index a20f1cf8559..8354eff6234 100644 --- a/qa/spec/runtime/api/request_spec.rb +++ b/qa/spec/runtime/api/request_spec.rb @@ -22,6 +22,12 @@ describe QA::Runtime::API::Request do end end + describe '#mask_url' do + it 'returns the full API request url with the token masked' do + expect(request.mask_url).to eq 'http://example.com/api/v4/users?private_token=[****]' + end + end + describe '#request_path' do it 'prepends the api path' do expect(request.request_path('/users')).to eq '/api/v4/users' diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index f2e5eb35871..0cfb9a70c88 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -341,7 +341,7 @@ describe QA::Runtime::Env do end end - describe '.dot_com?' do + describe '.address_matches?' do it 'returns true when url has .com' do QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") @@ -351,7 +351,45 @@ describe QA::Runtime::Env do it 'returns false when url does not have .com' do QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.test") - expect(described_class.dot_com?).to be_falsy + expect(described_class.dot_com?).to be_falsey + end + + context 'with arguments' do + it 'returns true when :subdomain is set' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.dot_com?(subdomain: :staging)).to be_truthy + end + + it 'matches multiple subdomains' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.address_matches?(subdomain: [:release, :staging])).to be_truthy + expect(described_class.address_matches?(:production, subdomain: [:release, :staging])).to be_truthy + end + + it 'matches :production' do + QA::Runtime::Scenario.define(:gitlab_address, "https://gitlab.com/") + + expect(described_class.address_matches?(:production)).to be_truthy + end + + it 'doesnt match with mismatching switches' do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.test') + + aggregate_failures do + expect(described_class.address_matches?(tld: '.net')).to be_falsey + expect(described_class.address_matches?(:production)).to be_falsey + expect(described_class.address_matches?(subdomain: [:staging])).to be_falsey + expect(described_class.address_matches?(domain: 'example')).to be_falsey + end + end + end + + it 'returns false for mismatching' do + QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") + + expect(described_class.address_matches?(:production)).to be_falsey end end end diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb index 1f09c3f73ac..9686a9771c4 100644 --- a/qa/spec/specs/helpers/quarantine_spec.rb +++ b/qa/spec/specs/helpers/quarantine_spec.rb @@ -124,7 +124,7 @@ describe QA::Specs::Helpers::Quarantine do end end - describe '.skip_or_run_quarantined_tests' do + describe '.skip_or_run_quarantined_tests_or_contexts' do context 'with no tag focused' do before do described_class.configure_rspec @@ -148,6 +148,37 @@ describe QA::Specs::Helpers::Quarantine do expect(group.examples.first.execution_result.status).to eq(:passed) end + context 'with environment set' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com') + described_class.configure_rspec + end + + it 'is skipped when set on contexts or descriptions' do + group = describe_successfully 'Quarantined in staging', quarantine: { only: { subdomain: :staging } } do + it('runs in staging') {} + end + + expect(group.examples.first.execution_result.status).to eq(:pending) + expect(group.examples.first.execution_result.pending_message) + .to eq('In quarantine') + end + + it 'is skipped only in staging' do + group = describe_successfully do + it('skipped in staging', quarantine: { only: { subdomain: :staging } }) {} + it('runs in staging', quarantine: { only: :production }) {} + it('skipped in staging also', quarantine: { only: { subdomain: %i[release staging] } }) {} + it('runs in any env') {} + end + + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + end + context 'quarantine message' do shared_examples 'test with quarantine message' do |quarantine_tag| it 'outputs the quarantine message' do @@ -280,4 +311,94 @@ describe QA::Specs::Helpers::Quarantine do end end end + + describe 'running against specific environments' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://staging.gitlab.com') + described_class.configure_rspec + end + + describe 'description and context blocks' do + context 'with environment set' do + it 'can apply to contexts or descriptions' do + group = describe_successfully 'Runs in staging', only: { subdomain: :staging } do + it('runs in staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + end + end + + context 'with different environment set' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com') + described_class.configure_rspec + end + + it 'does not run against production' do + group = describe_successfully 'Runs in staging', :something, only: { subdomain: :staging } do + it('runs in staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:pending) + end + end + end + + it 'runs only in staging' do + group = describe_successfully do + it('runs in staging', only: { subdomain: :staging }) {} + it('doesnt run in staging', only: :production) {} + it('runs in staging also', only: { subdomain: %i[release staging] }) {} + it('runs in any env') {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + + context 'custom env' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://release.gitlab.net') + end + + it 'runs on a custom environment' do + group = describe_successfully do + it('runs on release gitlab net', only: { tld: '.net', subdomain: :release, domain: 'gitlab' } ) {} + it('does not run on release', only: :production ) {} + end + + expect(group.examples.first.execution_result.status).to eq(:passed) + expect(group.examples.last.execution_result.status).to eq(:pending) + end + end + + context 'production' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/') + end + + it 'runs on production' do + group = describe_successfully do + it('runs on prod', only: :production ) {} + it('does not run in prod', only: { subdomain: :staging }) {} + it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + end + end + + it 'outputs a message for invalid environments' do + group = describe_successfully do + it('will skip', only: :production) {} + end + + expect(group.examples.first.execution_result.pending_message).to match(/[Tt]est.*not compatible.*environment/) + end + end end |