summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 11:18:50 +0000
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /qa
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
downloadgitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'qa')
-rw-r--r--qa/Dockerfile28
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock6
-rw-r--r--qa/qa.rb35
-rw-r--r--qa/qa/flow/project.rb12
-rw-r--r--qa/qa/git/repository.rb2
-rw-r--r--qa/qa/page/base.rb25
-rw-r--r--qa/qa/page/component/issue_board/show.rb138
-rw-r--r--qa/qa/page/component/web_ide/modal/create_new_file.rb19
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb17
-rw-r--r--qa/qa/page/dashboard/snippet/show.rb38
-rw-r--r--qa/qa/page/group/new.rb2
-rw-r--r--qa/qa/page/issuable/new.rb58
-rw-r--r--qa/qa/page/merge_request/new.rb53
-rw-r--r--qa/qa/page/merge_request/show.rb89
-rw-r--r--qa/qa/page/project/fork/new.rb4
-rw-r--r--qa/qa/page/project/issue/new.rb18
-rw-r--r--qa/qa/page/project/members.rb60
-rw-r--r--qa/qa/page/project/menu.rb14
-rw-r--r--qa/qa/page/project/new_experiment.rb26
-rw-r--r--qa/qa/page/project/operations/metrics/show.rb3
-rw-r--r--qa/qa/page/project/settings/advanced.rb4
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb2
-rw-r--r--qa/qa/page/project/settings/integrations.rb7
-rw-r--r--qa/qa/page/project/settings/main.rb2
-rw-r--r--qa/qa/page/project/settings/members.rb62
-rw-r--r--qa/qa/page/project/settings/services/jira.rb60
-rw-r--r--qa/qa/page/project/snippet/new.rb29
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb9
-rw-r--r--qa/qa/page/project/web_ide/edit.rb22
-rw-r--r--qa/qa/page/project/wiki/edit.rb30
-rw-r--r--qa/qa/page/project/wiki/new.rb61
-rw-r--r--qa/qa/page/project/wiki/show.rb57
-rw-r--r--qa/qa/resource/group.rb5
-rw-r--r--qa/qa/resource/issue.rb4
-rw-r--r--qa/qa/resource/project.rb36
-rw-r--r--qa/qa/resource/project_milestone.rb7
-rw-r--r--qa/qa/resource/project_snippet.rb29
-rw-r--r--qa/qa/resource/protected_branch.rb33
-rw-r--r--qa/qa/resource/repository/project_push.rb4
-rw-r--r--qa/qa/resource/repository/push.rb6
-rw-r--r--qa/qa/resource/repository/wiki_push.rb17
-rw-r--r--qa/qa/resource/sandbox.rb1
-rw-r--r--qa/qa/resource/ssh_key.rb17
-rw-r--r--qa/qa/resource/wiki.rb48
-rw-r--r--qa/qa/resource/wiki/project_page.rb68
-rw-r--r--qa/qa/runtime/api/repository_storage_moves.rb33
-rw-r--r--qa/qa/runtime/env.rb12
-rw-r--r--qa/qa/runtime/feature.rb37
-rw-r--r--qa/qa/scenario/test/instance/airgapped.rb19
-rw-r--r--qa/qa/service/docker_run/base.rb9
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb19
-rw-r--r--qa/qa/service/docker_run/jenkins.rb8
-rw-r--r--qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb53
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb21
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb105
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb101
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb (renamed from qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb)4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb39
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb42
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb93
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb111
-rw-r--r--qa/qa/support/api.rb23
-rw-r--r--qa/qa/support/repeater.rb5
-rw-r--r--qa/qa/vendor/jira/jira_api.rb54
-rw-r--r--qa/spec/runtime/feature_spec.rb31
-rw-r--r--qa/spec/scenario/test/instance/airgapped_spec.rb8
-rw-r--r--qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb71
77 files changed, 1797 insertions, 503 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 155a9761a6f..7f90e4bf5bf 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,6 +1,13 @@
FROM ruby:2.6-stretch
LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
-ENV DEBIAN_FRONTEND noninteractive
+
+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_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
@@ -21,28 +28,31 @@ RUN apt-get -y -t stretch-backports install git git-lfs
##
# Install Docker
#
-RUN wget -q https://download.docker.com/linux/static/stable/x86_64/docker-17.09.0-ce.tgz && \
- tar -zxf docker-17.09.0-ce.tgz && mv docker/docker /usr/local/bin/docker && \
- rm docker-17.09.0-ce.tgz
+RUN wget -q "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" && \
+ tar -zxf "docker-${DOCKER_VERSION}.tgz" && mv docker/docker /usr/local/bin/docker && \
+ rm "docker-${DOCKER_VERSION}.tgz"
##
# Install Google Chrome version with headless support
+# Download from our local S3 bucket, populated by https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/scripts/cache-google-chrome
#
-RUN curl -sS -L https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
-RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
-RUN apt-get update -q && apt-get install -y google-chrome-stable && apt-get clean
+RUN curl --silent --show-error --fail -O "${CHROME_URL}" && \
+ dpkg -i "./${CHROME_DEB}" || true && \
+ apt-get install -f -y && \
+ rm -f "./${CHROME_DEB}"
##
# Install chromedriver to make it work with Selenium
#
-RUN wget -q https://chromedriver.storage.googleapis.com/$(wget -q -O - https://chromedriver.storage.googleapis.com/LATEST_RELEASE)/chromedriver_linux64.zip
+RUN wget -q "https://chromedriver.storage.googleapis.com/${CHROME_DRIVER_VERSION}/chromedriver_linux64.zip"
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=v1.3.4 bash
+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
diff --git a/qa/Gemfile b/qa/Gemfile
index 6eb8733ab41..d5c682ef76f 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa'
-gem 'activesupport', '~> 6.0.3' # This should stay in sync with the root's Gemfile
+gem 'activesupport', '~> 6.0.3.1' # This should stay in sync with the root's Gemfile
gem 'capybara', '~> 3.29.0'
gem 'capybara-screenshot', '~> 1.0.23'
gem 'rake', '~> 12.3.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 9aeba236c96..23324fccdec 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
- activesupport (6.0.3)
+ activesupport (6.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -54,7 +54,7 @@ GEM
mime-types-data (3.2020.0425)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
- minitest (5.14.0)
+ minitest (5.14.1)
netrc (0.11.0)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
@@ -116,7 +116,7 @@ PLATFORMS
ruby
DEPENDENCIES
- activesupport (~> 6.0.3)
+ activesupport (~> 6.0.3.1)
airborne (~> 0.3.4)
capybara (~> 3.29.0)
capybara-screenshot (~> 1.0.23)
diff --git a/qa/qa.rb b/qa/qa.rb
index fe22b5792c3..4649f452c6f 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -43,6 +43,7 @@ module QA
module API
autoload :Client, 'qa/runtime/api/client'
+ autoload :RepositoryStorageMoves, 'qa/runtime/api/repository_storage_moves'
autoload :Request, 'qa/runtime/api/request'
end
@@ -79,15 +80,16 @@ module QA
autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone'
autoload :Members, 'qa/resource/members'
- autoload :Wiki, 'qa/resource/wiki'
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
autoload :Snippet, 'qa/resource/snippet'
autoload :Tag, 'qa/resource/tag'
autoload :ProjectMember, 'qa/resource/project_member'
+ autoload :ProjectSnippet, 'qa/resource/project_snippet'
autoload :UserGPG, 'qa/resource/user_gpg'
autoload :Visibility, 'qa/resource/visibility'
+ autoload :ProjectSnippet, 'qa/resource/project_snippet'
module KubernetesCluster
autoload :Base, 'qa/resource/kubernetes_cluster/base'
@@ -109,6 +111,10 @@ module QA
module Settings
autoload :HashedStorage, 'qa/resource/settings/hashed_storage'
end
+
+ module Wiki
+ autoload :ProjectPage, 'qa/resource/wiki/project_page'
+ end
end
##
@@ -131,6 +137,7 @@ module QA
module Instance
autoload :All, 'qa/scenario/test/instance/all'
autoload :Smoke, 'qa/scenario/test/instance/smoke'
+ autoload :Airgapped, 'qa/scenario/test/instance/airgapped'
end
module Integration
@@ -224,9 +231,11 @@ module QA
module Project
autoload :New, 'qa/page/project/new'
+ autoload :NewExperiment, 'qa/page/project/new_experiment'
autoload :Show, 'qa/page/project/show'
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'
+ autoload :Members, 'qa/page/project/members'
module Branches
autoload :Show, 'qa/page/project/branches/show'
@@ -254,6 +263,7 @@ module QA
autoload :Main, 'qa/page/project/settings/main'
autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd'
+ autoload :Integrations, 'qa/page/project/settings/integrations'
autoload :GeneralPipelines, 'qa/page/project/settings/general_pipelines'
autoload :AutoDevops, 'qa/page/project/settings/auto_devops'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
@@ -262,9 +272,12 @@ module QA
autoload :CiVariables, 'qa/page/project/settings/ci_variables'
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
- autoload :Members, 'qa/page/project/settings/members'
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions'
+
+ module Services
+ autoload :Jira, 'qa/page/project/settings/services/jira'
+ end
autoload :Operations, 'qa/page/project/settings/operations'
autoload :Incidents, 'qa/page/project/settings/incidents'
autoload :Integrations, 'qa/page/project/settings/integrations'
@@ -319,7 +332,6 @@ module QA
module Wiki
autoload :Edit, 'qa/page/project/wiki/edit'
- autoload :New, 'qa/page/project/wiki/new'
autoload :Show, 'qa/page/project/wiki/show'
autoload :GitAccess, 'qa/page/project/wiki/git_access'
end
@@ -327,6 +339,10 @@ module QA
module WebIDE
autoload :Edit, 'qa/page/project/web_ide/edit'
end
+
+ module Snippet
+ autoload :New, 'qa/page/project/snippet/new'
+ end
end
module Profile
@@ -339,6 +355,7 @@ module QA
end
module Issuable
+ autoload :New, 'qa/page/issuable/new'
autoload :Sidebar, 'qa/page/issuable/sidebar'
end
@@ -427,8 +444,16 @@ module QA
autoload :Common, 'qa/page/component/issuable/common'
end
+ module IssueBoard
+ autoload :Show, 'qa/page/component/issue_board/show'
+ end
+
module WebIDE
autoload :Alert, 'qa/page/component/web_ide/alert'
+
+ module Modal
+ autoload :CreateNewFile, 'qa/page/component/web_ide/modal/create_new_file'
+ end
end
module Project
@@ -512,6 +537,10 @@ module QA
autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job'
end
end
+
+ module Jira
+ autoload :JiraAPI, 'qa/vendor/jira/jira_api'
+ end
end
# Classes that provide support to other parts of the framework.
diff --git a/qa/qa/flow/project.rb b/qa/qa/flow/project.rb
index 72b9357a604..db42a3a3594 100644
--- a/qa/qa/flow/project.rb
+++ b/qa/qa/flow/project.rb
@@ -8,12 +8,20 @@ module QA
def add_member(project:, username:)
project.visit!
- Page::Project::Menu.perform(&:go_to_members_settings)
+ Page::Project::Menu.perform(&:click_members)
- Page::Project::Settings::Members.perform do |member_settings|
+ Page::Project::Members.perform do |member_settings|
member_settings.add_member(username)
end
end
+
+ def go_to_create_project_from_template
+ if Page::Project::NewExperiment.perform(&:shown?)
+ Page::Project::NewExperiment.perform(&:click_create_from_template_link)
+ else
+ Page::Project::New.perform(&:click_create_from_template_tab)
+ end
+ end
end
end
end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index bd2fbbd80cb..43608827b2e 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -111,7 +111,7 @@ module QA
end
def commit_with_gpg(message)
- run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(which gpg) && git commit -S -m "#{message}"}).to_s
+ run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s
end
def push_changes(branch = 'master')
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index cb3827f8eb1..f0d4ae45ef8 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -99,7 +99,16 @@ module QA
def find_element(name, **kwargs)
wait_for_requests
- find(element_selector_css(name), kwargs)
+ element_selector = element_selector_css(name, reject_capybara_query_keywords(kwargs))
+ find(element_selector, only_capybara_query_keywords(kwargs))
+ end
+
+ def only_capybara_query_keywords(kwargs)
+ kwargs.select { |kwarg| Capybara::Queries::SelectorQuery::VALID_KEYS.include?(kwarg) }
+ end
+
+ def reject_capybara_query_keywords(kwargs)
+ kwargs.reject { |kwarg| Capybara::Queries::SelectorQuery::VALID_KEYS.include?(kwarg) }
end
def active_element?(name)
@@ -162,11 +171,17 @@ module QA
def has_element?(name, **kwargs)
wait_for_requests
- wait = kwargs.delete(:wait) || Capybara.default_max_wait_time
- text = kwargs.delete(:text)
- klass = kwargs.delete(:class)
+ disabled = kwargs.delete(:disabled)
- has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass)
+ if disabled.nil?
+ wait = kwargs.delete(:wait) || Capybara.default_max_wait_time
+ text = kwargs.delete(:text)
+ klass = kwargs.delete(:class)
+
+ has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass)
+ else
+ find_element(name, kwargs).disabled? == disabled
+ end
end
def has_no_element?(name, **kwargs)
diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb
new file mode 100644
index 00000000000..0c840eba7ce
--- /dev/null
+++ b/qa/qa/page/component/issue_board/show.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module IssueBoard
+ class Show < QA::Page::Base
+ view 'app/assets/javascripts/boards/components/board_card.vue' do
+ element :board_card
+ end
+
+ view 'app/assets/javascripts/boards/components/board_form.vue' do
+ element :board_name_field
+ end
+
+ view 'app/assets/javascripts/boards/components/board_list.vue' do
+ element :board_list_cards_area
+ end
+
+ view 'app/assets/javascripts/boards/components/boards_selector.vue' do
+ element :boards_dropdown
+ element :boards_dropdown_content
+ element :create_new_board_button
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/deprecated_modal.vue' do
+ element :save_changes_button
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue' do
+ element :labels_dropdown_content
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do
+ element :labels_edit_button
+ end
+
+ view 'app/views/shared/boards/_show.html.haml' do
+ 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
+
+ # The `focused_board` method does not use `find_element` with an element defined
+ # with the attribute `data-qa-selector` since such element is not unique when the
+ # `is-focused` class is not set, and it was not possible to find a better solution.
+ def focused_board
+ find('.issue-boards-content.js-focus-mode-board.is-focused')
+ end
+
+ def boards_dropdown
+ find_element(:boards_dropdown)
+ end
+
+ def boards_dropdown_content
+ find_element(:boards_dropdown_content)
+ end
+
+ def boards_list_cards_area_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_list_cards_area)
+ end
+ end
+ end
+
+ def boards_list_header_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_list_header)
+ end
+ end
+ end
+
+ def card_of_list_with_index(index)
+ wait_boards_list_finish_loading do
+ within_element_by_index(:board_list, index) do
+ find_element(:board_card)
+ end
+ end
+ end
+
+ def click_boards_dropdown_button
+ # The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`,
+ # so it wasn't possible to add a `data-qa-selector` to it.
+ find_element(:boards_dropdown).find('button').click
+ end
+
+ def click_focus_mode_button
+ click_element(:focus_mode_button)
+ end
+
+ def configure_by_label(label)
+ click_boards_config_button
+ click_element(:labels_edit_button)
+ find_element(:labels_dropdown_content).find('li', text: label).click
+ click_element(:save_changes_button)
+ wait_boards_list_finish_loading
+ end
+
+ def create_new_board(board_name)
+ click_boards_dropdown_button
+ click_element(:create_new_board_button)
+ set_name(board_name)
+ end
+
+ def has_modal_board_name_field?
+ has_element?(:board_name_field, wait: 1)
+ end
+
+ def set_name(name)
+ find_element(:board_name_field).set(name)
+ click_element(:save_changes_button)
+ end
+
+ private
+
+ def wait_boards_list_finish_loading
+ within_element(:boards_list) do
+ wait_until(reload: false, max_duration: 5, sleep_interval: 1) do
+ finished_loading? && (block_given? ? yield : true)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Component::IssueBoard::Show.prepend_if_ee('QA::EE::Page::Component::IssueBoard::Show')
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
new file mode 100644
index 00000000000..48eb32fefd6
--- /dev/null
+++ b/qa/qa/page/component/web_ide/modal/create_new_file.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module WebIDE
+ module Modal
+ class CreateNewFile < Page::Base
+ 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
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb
index d1a194ba1db..bcfb4734b59 100644
--- a/qa/qa/page/dashboard/snippet/new.rb
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -6,6 +6,7 @@ module QA
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
@@ -14,12 +15,8 @@ module QA
element :description_placeholder, required: true
end
- view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
- element :snippet_title, required: true
- end
-
view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do
- element :snippet_file_name
+ element :file_name_field
end
view 'app/views/shared/form_elements/_description.html.haml' do
@@ -29,18 +26,18 @@ module QA
view 'app/views/shared/snippets/_form.html.haml' do
element :snippet_description_field
element :description_placeholder
- element :snippet_title
- element :snippet_file_name
+ element :snippet_title_field
+ element :file_name_field
element :submit_button
end
- view 'app/views/projects/_zen.html.haml' do
+ 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, title
+ fill_element :snippet_title_field, title
end
def fill_description(description)
@@ -54,7 +51,7 @@ module QA
def fill_file_name(name)
finished_loading?
- fill_element :snippet_file_name, name
+ fill_element :file_name_field, name
end
def fill_file_content(content)
diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb
index d43b64cd1d4..da494ae70ec 100644
--- a/qa/qa/page/dashboard/snippet/show.rb
+++ b/qa/qa/page/dashboard/snippet/show.rb
@@ -6,32 +6,26 @@ module QA
module Snippet
class Show < Page::Base
view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do
- element :snippet_description_field
+ element :snippet_description_content
end
view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
- element :snippet_title, required: true
+ element :snippet_title_content, required: true
end
- view 'app/views/shared/snippets/_header.html.haml' do
- element :snippet_title, required: true
- element :snippet_description_field, required: true
- element :snippet_box
- end
-
- view 'app/views/projects/blob/_header_content.html.haml' do
- element :file_title_name
+ 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_name
+ element :file_title_content
end
- view 'app/views/shared/_file_highlight.html.haml' do
+ view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do
element :file_content
end
- view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do
+ view 'app/assets/javascripts/blob/components/blob_content.vue' do
element :file_content
end
@@ -50,21 +44,25 @@ module QA
end
def has_snippet_title?(snippet_title)
- has_element? :snippet_title, text: snippet_title
+ has_element? :snippet_title_content, text: snippet_title
end
def has_snippet_description?(snippet_description)
- has_element? :snippet_description_field, text: 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_box) do
+ within_element(:snippet_container) do
has_text?(visibility_type)
end
end
def has_file_name?(file_name)
- within_element(:file_title_name) do
+ within_element(:file_title_content) do
has_text?(file_name)
end
end
@@ -85,7 +83,11 @@ module QA
finished_loading?
click_element(:snippet_action_button, action: 'Delete')
click_element(:delete_snippet_button)
- finished_loading? # wait for the page to reload after deletion
+ # 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
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index e01b8754d55..88e7121dbe0 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -10,7 +10,7 @@ module QA
element :group_description_field, 'text_area :description' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/groups/new.html.haml' do
+ view 'app/views/groups/_new_group_fields.html.haml' do
element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/issuable/new.rb b/qa/qa/page/issuable/new.rb
new file mode 100644
index 00000000000..7891074092e
--- /dev/null
+++ b/qa/qa/page/issuable/new.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Issuable
+ class New < Page::Base
+ view 'app/views/shared/issuable/form/_title.html.haml' do
+ element :issuable_form_title
+ end
+
+ view 'app/views/shared/issuable/form/_metadata.html.haml' do
+ element :issuable_milestone_dropdown
+ end
+
+ view 'app/views/shared/form_elements/_description.html.haml' do
+ element :issuable_form_description
+ end
+
+ view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do
+ element :issuable_dropdown_menu_milestone
+ end
+
+ view 'app/views/shared/issuable/_label_dropdown.html.haml' do
+ element :issuable_label
+ end
+
+ view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do
+ element :assign_to_me_link
+ end
+
+ def fill_title(title)
+ fill_element :issuable_form_title, title
+ end
+
+ def fill_description(description)
+ fill_element :issuable_form_description, description
+ end
+
+ def choose_milestone(milestone)
+ click_element :issuable_milestone_dropdown
+ within_element(:issuable_dropdown_menu_milestone) do
+ click_on milestone.title
+ end
+ end
+
+ def select_label(label)
+ click_element :issuable_label
+
+ click_link label.title
+ end
+
+ def assign_to_me
+ click_element :assign_to_me_link
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index f877ba76b38..eda7da89a35 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -3,62 +3,13 @@
module QA
module Page
module MergeRequest
- class New < Page::Base
+ class New < Page::Issuable::New
view 'app/views/shared/issuable/_form.html.haml' do
element :issuable_create_button
end
- view 'app/views/shared/issuable/form/_title.html.haml' do
- element :issuable_form_title
- end
-
- view 'app/views/shared/issuable/form/_metadata.html.haml' do
- element :issuable_milestone_dropdown
- end
-
- view 'app/views/shared/form_elements/_description.html.haml' do
- element :issuable_form_description
- end
-
- view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do
- element :issuable_dropdown_menu_milestone
- end
-
- view 'app/views/shared/issuable/_label_dropdown.html.haml' do
- element :issuable_label
- end
-
- view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do
- element :assign_to_me_link
- end
-
def create_merge_request
- click_element :issuable_create_button
- end
-
- def fill_title(title)
- fill_element :issuable_form_title, title
- end
-
- def fill_description(description)
- fill_element :issuable_form_description, description
- end
-
- def choose_milestone(milestone)
- click_element :issuable_milestone_dropdown
- within_element(:issuable_dropdown_menu_milestone) do
- click_on milestone.title
- end
- end
-
- def select_label(label)
- click_element :issuable_label
-
- click_link label.title
- end
-
- def assign_to_me
- click_element :assign_to_me_link
+ click_element :issuable_create_button, Page::MergeRequest::Show
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 0da40b35938..07f8e568b2a 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -73,6 +73,70 @@ module QA
element :edit_button
end
+ view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do
+ element :submit_review
+ end
+
+ view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do
+ element :review_bar
+ element :discard_review
+ element :modal_delete_pending_comments
+ end
+
+ view 'app/assets/javascripts/notes/components/note_form.vue' do
+ element :unresolve_review_discussion_checkbox
+ element :resolve_review_discussion_checkbox
+ element :start_review
+ element :comment_now
+ end
+
+ view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
+ element :review_preview_toggle
+ end
+
+ def start_review
+ click_element :start_review
+
+ # After clicking the button, wait for it to disappear
+ # before moving on to the next part of the test
+ has_no_element? :start_review
+ end
+
+ def comment_now
+ click_element :comment_now
+
+ # After clicking the button, wait for it to disappear
+ # before moving on to the next part of the test
+ has_no_element? :comment_now
+ end
+
+ def submit_pending_reviews
+ within_element :review_bar do
+ click_element :review_preview_toggle
+ click_element :submit_review
+
+ # After clicking the button, wait for it to disappear
+ # before moving on to the next part of the test
+ has_no_element? :submit_review
+ end
+ end
+
+ def discard_pending_reviews
+ within_element :review_bar do
+ click_element :discard_review
+ end
+ click_element :modal_delete_pending_comments
+ end
+
+ def resolve_review_discussion
+ scroll_to_element :start_review
+ check_element :resolve_review_discussion_checkbox
+ end
+
+ def unresolve_review_discussion
+ check_element :unresolve_review_discussion_checkbox
+ end
+
def add_comment_to_diff(text)
wait_until(sleep_interval: 5) do
has_text?("No newline at end of file")
@@ -154,8 +218,8 @@ module QA
end
def merge!
- click_element :merge_button if ready_to_merge?
-
+ wait_until_ready_to_merge
+ click_element(:merge_button)
finished_loading?
raise "Merge did not appear to be successful" unless merged?
@@ -165,11 +229,18 @@ module QA
has_element?(:merged_status_content, text: 'The changes were merged into', wait: 30)
end
- def ready_to_merge?
- # The merge button is disabled on load
- wait_until do
- has_element?(:merge_button)
- end
+ # Check if the MR is able to be merged
+ # Waits up 10 seconds and returns false if the MR can't be merged
+ def mergeable?
+ # The merge button is enabled via JS, but `has_element?` calls
+ # `wait_for_requests`, which should ensure the disabled/enabled
+ # state of the element is reliable
+ has_element?(:merge_button, disabled: false)
+ end
+
+ # Waits up 60 seconds and raises an error if unable to merge
+ def wait_until_ready_to_merge
+ has_element?(:merge_button)
# The merge button is enabled via JS
wait_until(reload: false) do
@@ -198,7 +269,9 @@ module QA
end
def try_to_merge!
- click_element :merge_button if ready_to_merge?
+ wait_until_ready_to_merge
+
+ click_element(:merge_button)
end
def view_email_patches
diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb
index 1a52c61551d..49c2205fd08 100644
--- a/qa/qa/page/project/fork/new.rb
+++ b/qa/qa/page/project/fork/new.rb
@@ -6,11 +6,11 @@ module QA
module Fork
class New < Page::Base
view 'app/views/projects/forks/_fork_button.html.haml' do
- element :namespace, 'link_to project_forks_path' # rubocop:disable QA/ElementWithPattern
+ element :fork_namespace_content
end
def choose_namespace(namespace = Runtime::Namespace.path)
- click_on namespace
+ click_element(:fork_namespace_content, name: namespace)
end
end
end
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 65c02801d67..c90a09dce2e 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -4,27 +4,11 @@ module QA
module Page
module Project
module Issue
- class New < Page::Base
+ class New < Page::Issuable::New
view 'app/views/shared/issuable/_form.html.haml' do
element :issuable_create_button
end
- view 'app/views/shared/issuable/form/_title.html.haml' do
- element :issue_title_textbox, 'form.text_field :title' # rubocop:disable QA/ElementWithPattern
- end
-
- view 'app/views/shared/form_elements/_description.html.haml' do
- element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" # rubocop:disable QA/ElementWithPattern
- end
-
- def add_title(title)
- fill_in 'issue_title', with: title
- end
-
- def add_description(description)
- fill_in 'issue_description', with: description
- end
-
def create_new_issue
click_element :issuable_create_button, Page::Project::Issue::Show
end
diff --git a/qa/qa/page/project/members.rb b/qa/qa/page/project/members.rb
new file mode 100644
index 00000000000..88b05ceb1d1
--- /dev/null
+++ b/qa/qa/page/project/members.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ class Members < Page::Base
+ include QA::Page::Component::Select2
+
+ view 'app/views/shared/members/_invite_member.html.haml' do
+ element :member_select_field
+ element :invite_member_button
+ end
+
+ view 'app/views/projects/project_members/_team.html.haml' do
+ element :members_list
+ end
+
+ view 'app/views/projects/project_members/index.html.haml' do
+ element :invite_group_tab
+ end
+
+ view 'app/views/shared/members/_invite_group.html.haml' do
+ element :group_select_field
+ element :invite_group_button
+ end
+
+ view 'app/views/shared/members/_group.html.haml' do
+ element :group_row
+ element :delete_group_access_link
+ end
+
+ def select_group(group_name)
+ click_element :group_select_field
+ search_and_select(group_name)
+ end
+
+ def invite_group(group_name)
+ click_element :invite_group_tab
+ select_group(group_name)
+ click_element :invite_group_button
+ end
+
+ def add_member(username)
+ click_element :member_select_field
+ search_and_select username
+ click_element :invite_member_button
+ end
+
+ def remove_group(group_name)
+ click_element :invite_group_tab
+ page.accept_alert do
+ within_element(:group_row, text: group_name) do
+ click_element :delete_group_access_link
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 5967213a52b..3d4d0ff9d22 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -16,6 +16,8 @@ module QA
element :activity_link
element :merge_requests_link
element :wiki_link
+ element :snippets_link
+ element :members_link
end
def click_merge_requests
@@ -35,6 +37,18 @@ module QA
click_element(:activity_link)
end
end
+
+ def click_snippets
+ within_sidebar do
+ click_element(:snippets_link)
+ end
+ end
+
+ def click_members
+ within_sidebar do
+ click_element(:members_link)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/new_experiment.rb b/qa/qa/page/project/new_experiment.rb
new file mode 100644
index 00000000000..813f7f6cefe
--- /dev/null
+++ b/qa/qa/page/project/new_experiment.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ class NewExperiment < Page::Base
+ view 'app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue' do
+ element :blank_project_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern
+ element :create_from_template_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern
+ end
+
+ def shown?
+ has_element? :blank_project_link
+ end
+
+ def click_blank_project_link
+ click_element :blank_project_link
+ end
+
+ def click_create_from_template_link
+ click_element :create_from_template_link
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb
index 2228cca1d3d..a1c15e72f44 100644
--- a/qa/qa/page/project/operations/metrics/show.rb
+++ b/qa/qa/page/project/operations/metrics/show.rb
@@ -11,6 +11,9 @@ module QA
view 'app/assets/javascripts/monitoring/components/dashboard.vue' do
element :prometheus_graphs
+ end
+
+ view 'app/assets/javascripts/monitoring/components/dashboard_header.vue' do
element :dashboards_filter_dropdown
element :environments_dropdown
element :edit_dashboard_button
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 3bb5181a31c..d6e004e827e 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -43,7 +43,9 @@ module QA
def transfer_project!(project_name, namespace)
expand_select_list
- select_transfer_option(namespace)
+ # Workaround for a failure to search when there are no spaces around the /
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/218965
+ select_transfer_option(namespace.gsub(/([^\s])\/([^\s])/, '\1 / \2'))
click_element(:transfer_button)
fill_confirmation_text(project_name)
click_confirm_button
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index aa27c030b78..a7a0f6f57b6 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -42,5 +42,3 @@ module QA
end
end
end
-
-QA::Page::Project::Settings::CICD.prepend_if_ee('QA::EE::Page::Project::Settings::CICD')
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
index 436a42fb093..e18ff71bcb3 100644
--- a/qa/qa/page/project/settings/integrations.rb
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -7,13 +7,20 @@ module QA
class Integrations < QA::Page::Base
view 'app/views/shared/integrations/_index.html.haml' do
element :prometheus_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
+ element :jira_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
end
def click_on_prometheus_integration
click_element :prometheus_link
end
+
+ def click_jira_link
+ click_element :jira_link
+ end
end
end
end
end
end
+
+QA::Page::Project::Settings::Integrations.prepend_if_ee('QA::EE::Page::Project::Settings::Integrations')
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index efae497b6ba..880711770c0 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -58,3 +58,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::Main.prepend_if_ee("QA::EE::Page::Project::Settings::Main")
diff --git a/qa/qa/page/project/settings/members.rb b/qa/qa/page/project/settings/members.rb
deleted file mode 100644
index 5dc873750b0..00000000000
--- a/qa/qa/page/project/settings/members.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Settings
- class Members < Page::Base
- include QA::Page::Component::Select2
-
- view 'app/views/shared/members/_invite_member.html.haml' do
- element :member_select_field
- element :invite_member_button
- end
-
- view 'app/views/projects/project_members/_team.html.haml' do
- element :members_list
- end
-
- view 'app/views/projects/project_members/index.html.haml' do
- element :invite_group_tab
- end
-
- view 'app/views/shared/members/_invite_group.html.haml' do
- element :group_select_field
- element :invite_group_button
- end
-
- view 'app/views/shared/members/_group.html.haml' do
- element :group_row
- element :delete_group_access_link
- end
-
- def select_group(group_name)
- click_element :group_select_field
- search_and_select(group_name)
- end
-
- def invite_group(group_name)
- click_element :invite_group_tab
- select_group(group_name)
- click_element :invite_group_button
- end
-
- def add_member(username)
- click_element :member_select_field
- search_and_select username
- click_element :invite_member_button
- end
-
- def remove_group(group_name)
- click_element :invite_group_tab
- page.accept_alert do
- within_element(:group_row, text: group_name) do
- click_element :delete_group_access_link
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb
new file mode 100644
index 00000000000..9f9331bac94
--- /dev/null
+++ b/qa/qa/page/project/settings/services/jira.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ 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
+ end
+
+ view 'app/helpers/services_helper.rb' do
+ element :save_changes_button
+ end
+
+ def setup_service_with(url:)
+ QA::Runtime::Logger.info "Setting up JIRA"
+
+ set_jira_server_url(url)
+ set_username(Runtime::Env.jira_admin_username)
+ set_password(Runtime::Env.jira_admin_password)
+ set_transaction_ids('11,21,31,41')
+
+ click_save_changes_button
+ wait_until(reload: false) do
+ has_element?(:save_changes_button, wait: 1) ? !find_element(:save_changes_button).disabled? : true
+ end
+ end
+
+ private
+
+ def set_jira_server_url(url)
+ fill_element(:url_field, url)
+ end
+
+ def set_username(username)
+ fill_element(:username_field, username)
+ end
+
+ def set_password(password)
+ fill_element(:password_field, password)
+ end
+
+ def set_transaction_ids(transaction_ids)
+ fill_element(:jira_issue_transition_id_field, transaction_ids)
+ end
+
+ def click_save_changes_button
+ click_element :save_changes_button
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/snippet/new.rb b/qa/qa/page/project/snippet/new.rb
new file mode 100644
index 00000000000..1463dfc2c7f
--- /dev/null
+++ b/qa/qa/page/project/snippet/new.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Snippet
+ class New < Page::Dashboard::Snippet::New
+ include Component::LazyLoader
+ view 'app/views/shared/empty_states/_snippets.html.haml' do
+ element :create_first_snippet_link
+ element :svg_content
+ end
+
+ def click_create_first_snippet
+ finished_loading?
+ # The svg takes a fraction of a second to load after which the
+ # "New snippet" button shifts up a bit. This can cause
+ # webdriver to miss the hit so we wait for the svg to load before
+ # clicking the button.
+ within_element(:svg_content) do
+ has_element?(:js_lazy_loaded)
+ end
+ click_element(:create_first_snippet_link)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index 0dd4bd1817a..47274c8db54 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -15,7 +15,6 @@ module QA
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
- element :link_members_settings
element :general_settings_link
element :integrations_settings_link
element :operations_settings_link
@@ -31,14 +30,6 @@ module QA
end
end
- def go_to_members_settings
- hover_settings do
- within_submenu do
- click_element :link_members_settings
- end
- end
- end
-
def go_to_repository_settings
hover_settings do
within_submenu do
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 7809f9246ec..29f431d81df 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -20,12 +20,6 @@ module QA
element :file_list
end
- view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
- element :full_file_path
- element :new_file_modal
- element :template_list
- end
-
view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do
element :file_templates_bar
element :file_template_dropdown
@@ -52,6 +46,10 @@ module QA
element :editor_container
end
+ view 'app/assets/javascripts/ide/components/ide.vue' do
+ element :first_file_button
+ end
+
def has_file?(file_name)
within_element(:file_list) do
page.has_content? file_name
@@ -59,10 +57,7 @@ module QA
end
def create_new_file_from_template(file_name, template)
- click_element :new_file
-
- # Wait for the modal animation to complete before clicking on the file name
- wait_for_animated_element(:new_file_modal)
+ click_element(:new_file, Page::Component::WebIDE::Modal::CreateNewFile)
within_element(:template_list) do
click_on file_name
@@ -130,6 +125,13 @@ module QA
find('.modified textarea.inputarea')
end
end
+
+ def create_first_file(file_name)
+ finished_loading?
+ click_element(:first_file_button, Page::Component::WebIDE::Modal::CreateNewFile)
+ fill_element(:file_name_field, file_name)
+ click_button('Create file')
+ end
end
end
end
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index f6edc28c41a..96301e33733 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -5,14 +5,32 @@ module QA
module Project
module Wiki
class Edit < Page::Base
- view 'app/views/projects/wikis/_main_links.html.haml' do
- element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern
- element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern
- element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
+ view 'app/views/shared/wikis/_form.html.haml' do
+ element :wiki_title_textbox
+ element :wiki_content_textarea
+ element :wiki_message_textbox
+ element :save_changes_button
+ element :create_page_button
end
- def click_edit
- click_on 'Edit'
+ def set_title(title)
+ fill_element :wiki_title_textbox, title
+ end
+
+ def set_content(content)
+ fill_element :wiki_content_textarea, content
+ end
+
+ def set_message(message)
+ fill_element :wiki_message_textbox, message
+ end
+
+ def click_save_changes
+ click_element :save_changes_button
+ end
+
+ def click_create_page
+ click_element :create_page_button
end
end
end
diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb
deleted file mode 100644
index 792eba4bab7..00000000000
--- a/qa/qa/page/project/wiki/new.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Wiki
- class New < Page::Base
- include Component::LazyLoader
-
- view 'app/views/projects/wikis/_form.html.haml' do
- element :wiki_title_textbox
- element :wiki_content_textarea
- element :wiki_message_textbox
- element :save_changes_button
- element :create_page_button
- end
-
- view 'app/views/shared/empty_states/_wikis.html.haml' do
- element :create_first_page_link
- end
-
- view 'app/views/shared/empty_states/_wikis_layout.html.haml' do
- element :svg_content
- end
-
- def click_create_your_first_page_button
- # The svg takes a fraction of a second to load after which the
- # "Create your first page" button shifts up a bit. This can cause
- # webdriver to miss the hit so we wait for the svg to load before
- # clicking the button.
- within_element(:svg_content) do
- has_element? :js_lazy_loaded
- end
-
- click_element :create_first_page_link
- end
-
- def set_title(title)
- fill_element :wiki_title_textbox, title
- end
-
- def set_content(content)
- fill_element :wiki_content_textarea, content
- end
-
- def set_message(message)
- fill_element :wiki_message_textbox, message
- end
-
- def save_changes
- click_element :save_changes_button
- end
-
- def create_new_page
- click_element :create_page_button
- 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 44619d177b1..7e4e714f4a6 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -5,23 +5,70 @@ module QA
module Project
module Wiki
class Show < Page::Base
- include Page::Component::LegacyClonePanel
+ include Component::LazyLoader
- view 'app/views/projects/wikis/pages.html.haml' do
- element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
+ view 'app/views/shared/wikis/_sidebar.html.haml' do
+ element :clone_repository_link
end
- view 'app/views/projects/wikis/show.html.haml' do
+ view 'app/views/shared/wikis/show.html.haml' do
+ element :wiki_page_title
element :wiki_page_content
end
+ view 'app/views/shared/wikis/_main_links.html.haml' do
+ element :new_page_button
+ element :page_history_button
+ element :edit_page_button
+ end
+
+ view 'app/views/shared/empty_states/_wikis.html.haml' do
+ element :create_first_page_link
+ end
+
+ view 'app/views/shared/empty_states/_wikis_layout.html.haml' do
+ element :svg_content
+ end
+
+ def click_create_your_first_page
+ # The svg takes a fraction of a second to load after which the
+ # "Create your first page" button shifts up a bit. This can cause
+ # webdriver to miss the hit so we wait for the svg to load before
+ # clicking the button.
+ within_element(:svg_content) do
+ has_element? :js_lazy_loaded
+ end
+
+ click_element :create_first_page_link
+ end
+
+ def click_new_page
+ click_element(:new_page_button)
+ end
+
+ def click_page_history
+ click_element(:page_history_button)
+ end
+
+ def click_edit
+ click_element(:edit_page_button)
+ end
+
def click_clone_repository
- click_on '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
+
+ def has_content?(content)
+ has_element?(:wiki_page_content, content)
+ end
end
end
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index a30bb8cbc77..850d6205305 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -14,6 +14,7 @@ module QA
end
end
+ attribute :full_path
attribute :id
attribute :name
attribute :runners_token
@@ -74,10 +75,6 @@ module QA
def api_delete_path
"/groups/#{id}"
end
-
- def full_path
- sandbox.path + ' / ' + path
- end
end
end
end
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index 0817a9de06f..b4295a35263 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -32,8 +32,8 @@ module QA
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |new_page|
- new_page.add_title(@title)
- new_page.add_description(@description)
+ new_page.fill_title(@title)
+ new_page.fill_description(@description)
new_page.create_new_issue
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 78e2ba8a248..645f4e97ee0 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -30,6 +30,8 @@ module QA
"#{sandbox_path}#{group.path}/#{name}" if group
end
+ alias_method :full_path, :path_with_namespace
+
def sandbox_path
group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : ''
end
@@ -54,6 +56,8 @@ module QA
@auto_devops_enabled = false
@visibility = :public
@template_name = nil
+
+ self.name = "the_awesome_project"
end
def name=(raw_name)
@@ -67,12 +71,14 @@ module QA
end
if @template_name
+ QA::Flow::Project.go_to_create_project_from_template
Page::Project::New.perform do |new_page|
- new_page.click_create_from_template_tab
new_page.use_template_for_project(@template_name)
end
end
+ Page::Project::NewExperiment.perform(&:click_blank_project_link) if Page::Project::NewExperiment.perform(&:shown?)
+
Page::Project::New.perform do |new_page|
new_page.choose_test_namespace
new_page.choose_name(@name)
@@ -89,6 +95,10 @@ module QA
super
end
+ def has_file?(file_path)
+ repository_tree.any? { |file| file[:path] == file_path }
+ end
+
def api_get_path
"/projects/#{CGI.escape(path_with_namespace)}"
end
@@ -109,6 +119,14 @@ module QA
"#{api_get_path}/runners"
end
+ def api_repository_branches_path
+ "#{api_get_path}/repository/branches"
+ end
+
+ def api_repository_tree_path
+ "#{api_get_path}/repository/tree"
+ end
+
def api_pipelines_path
"#{api_get_path}/pipelines"
end
@@ -149,11 +167,9 @@ module QA
raise ResourceUpdateFailedError, "Could not change repository storage to #{new_storage}. Request returned (#{response.code}): `#{response}`."
end
- wait_until do
- reload!
-
- api_response[:repository_storage] == new_storage
- end
+ wait_until(sleep_interval: 1) { Runtime::API::RepositoryStorageMoves.has_status?(self, 'finished', new_storage) }
+ rescue Support::Repeater::RepeaterConditionExceededError
+ raise Runtime::API::RepositoryStorageMoves::RepositoryStorageMovesError, 'Timed out while waiting for the repository storage move to finish'
end
def import_status
@@ -180,6 +196,14 @@ module QA
parse_body(response)
end
+ def repository_branches
+ parse_body(get(Runtime::API::Request.new(api_client, api_repository_branches_path).url))
+ end
+
+ def repository_tree
+ parse_body(get(Runtime::API::Request.new(api_client, api_repository_tree_path).url))
+ end
+
def pipelines
parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url))
end
diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb
index 4d6b37937b4..385b9f0c96b 100644
--- a/qa/qa/resource/project_milestone.rb
+++ b/qa/qa/resource/project_milestone.rb
@@ -3,6 +3,8 @@
module QA
module Resource
class ProjectMilestone < Base
+ attr_writer :start_date, :due_date
+
attribute :id
attribute :title
@@ -27,7 +29,10 @@ module QA
def api_post_body
{
title: title
- }
+ }.tap do |hash|
+ hash[:start_date] = @start_date if @start_date
+ hash[:due_date] = @due_date if @due_date
+ end
end
end
end
diff --git a/qa/qa/resource/project_snippet.rb b/qa/qa/resource/project_snippet.rb
new file mode 100644
index 00000000000..ce4be6445f1
--- /dev/null
+++ b/qa/qa/resource/project_snippet.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class ProjectSnippet < Snippet
+ attribute :project do
+ Project.fabricate_via_api! do |resource|
+ resource.name = 'project-with-snippets'
+ end
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform { |sidebar| sidebar.click_snippets }
+
+ Page::Project::Snippet::New.perform do |new_snippet|
+ new_snippet.click_create_first_snippet
+ new_snippet.fill_title(@title)
+ new_snippet.fill_description(@description)
+ new_snippet.set_visibility(@visibility)
+ new_snippet.fill_file_name(@file_name)
+ new_snippet.fill_file_content(@file_content)
+ new_snippet.click_create_snippet_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb
index 9c65e0e5a31..9b728fc4c24 100644
--- a/qa/qa/resource/protected_branch.rb
+++ b/qa/qa/resource/protected_branch.rb
@@ -5,7 +5,12 @@ require 'securerandom'
module QA
module Resource
class ProtectedBranch < Base
- attr_accessor :branch_name, :allowed_to_push, :allowed_to_merge, :protected
+ attr_accessor :branch_name,
+ :allowed_to_push,
+ :allowed_to_merge,
+ :protected,
+ :new_branch,
+ :require_code_owner_approval
attribute :project do
Project.fabricate_via_api! do |resource|
@@ -21,11 +26,12 @@ module QA
project_push.commit_message = 'Add new file'
project_push.branch_name = branch_name
project_push.new_branch = true
- project_push.remote_branch = @branch_name
+ project_push.remote_branch = branch_name
end
end
def initialize
+ @new_branch = true
@branch_name = 'test/branch'
@allowed_to_push = {
roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
@@ -34,22 +40,29 @@ module QA
roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
}
@protected = false
+ @require_code_owner_approval = true
end
def fabricate!
- populate(:branch)
+ if new_branch
+ populate(:branch)
- project.wait_for_push_new_branch @branch_name
+ project.wait_for_push_new_branch branch_name
+ end
project.visit!
Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
- page.select_branch(branch_name)
- page.select_allowed_to_merge(allowed_to_merge)
- page.select_allowed_to_push(allowed_to_push)
- page.protect_branch
+ if new_branch
+ page.select_branch(branch_name)
+ page.select_allowed_to_merge(allowed_to_merge)
+ page.select_allowed_to_push(allowed_to_push)
+ page.protect_branch
+ else
+ page.require_code_owner_approval(branch_name) if require_code_owner_approval
+ end
end
end
end
@@ -59,11 +72,11 @@ module QA
end
def api_get_path
- "/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}"
+ "/projects/#{project.id}/protected_branches/#{branch_name}"
end
def api_delete_path
- "/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}"
+ "/projects/#{project.id}/protected_branches/#{branch_name}"
end
class Roles
diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb
index 17596601cf9..c46921ad0c7 100644
--- a/qa/qa/resource/repository/project_push.rb
+++ b/qa/qa/resource/repository/project_push.rb
@@ -9,8 +9,11 @@ module QA
attr_accessor :project_name
attr_writer :wait_for_push
+ attribute :group
+
attribute :project do
Project.fabricate! do |resource|
+ resource.group = group if @group
resource.name = project_name
resource.description = 'Project with repository'
end
@@ -24,6 +27,7 @@ module QA
@new_branch = true
@project_name = 'project-with-code'
@wait_for_push = true
+ @group = nil
end
def repository_http_uri
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index 902ae9f3135..1e5399fcc59 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -60,13 +60,13 @@ module QA
repository.use_lfs = use_lfs
- username = 'GitLab QA'
+ name = 'GitLab QA'
email = 'root@gitlab.com'
if user
repository.username = user.username
repository.password = user.password
- username = user.name
+ name = user.name
email = user.email
end
@@ -75,7 +75,7 @@ module QA
end
@output += repository.clone
- repository.configure_identity(username, email)
+ repository.configure_identity(name, email)
@output += repository.checkout(branch_name, new_branch: new_branch)
diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb
index e926c00d380..edf76c7cd78 100644
--- a/qa/qa/resource/repository/wiki_push.rb
+++ b/qa/qa/resource/repository/wiki_push.rb
@@ -5,17 +5,17 @@ module QA
module Repository
class WikiPush < Repository::Push
attribute :wiki do
- Wiki.fabricate! do |resource|
+ # We are using the project based wiki as a standard.
+ Wiki::ProjectPage.fabricate_via_api! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
- resource.message = 'Update home'
end
end
def initialize
@file_name = 'Home.md'
- @file_content = '# Welcome to My Wiki'
- @commit_message = 'Updating Home Page'
+ @file_content = 'This line was created using git push'
+ @commit_message = 'Updating using git push'
@branch_name = 'master'
@new_branch = false
end
@@ -28,9 +28,12 @@ module QA
@repository_ssh_uri ||= wiki.repository_ssh_location.uri
end
- def fabricate!
- super
- wiki.visit!
+ def web_url
+ # TODO
+ # workaround
+ # i.e. This replaces the last occurence of the string (case sensitive)
+ # and attaches everything before to the new substring
+ repository_http_uri.to_s.gsub(/(.*)\b\.wiki\.git\b/i, "\\1/-/wikis/#{@file_name.gsub('.md', '')}")
end
end
end
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 7b427af6b74..032ff65c58b 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -13,6 +13,7 @@ module QA
attribute :id
attribute :runners_token
+ attribute :name
def initialize
@path = Runtime::Namespace.sandbox_name
diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb
index b948bf3969b..317d70ef2c3 100644
--- a/qa/qa/resource/ssh_key.rb
+++ b/qa/qa/resource/ssh_key.rb
@@ -6,6 +6,7 @@ module QA
extend Forwardable
attr_reader :title
+ attr_accessor :expires_at
attribute :id
@@ -53,13 +54,27 @@ module QA
def api_post_body
{
title: title,
- key: public_key
+ key: public_key,
+ expires_at: expires_at
}
end
def api_delete_path
"/user/keys/#{id}"
end
+
+ def replicated?
+ api_client = Runtime::API::Client.new(:geo_secondary)
+
+ QA::Runtime::Logger.debug('Checking for SSH key replication')
+
+ Support::Retrier.retry_until(max_duration: QA::EE::Runtime::Geo.max_db_replication_time, sleep_interval: 3) do
+ response = get Runtime::API::Request.new(api_client, api_get_path).url
+
+ response.code == QA::Support::Api::HTTP_STATUS_OK &&
+ parse_body(response)[:title].include?(title)
+ end
+ end
end
end
end
diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb
deleted file mode 100644
index 45d5da9346d..00000000000
--- a/qa/qa/resource/wiki.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- class Wiki < Base
- attr_accessor :title, :content, :message
-
- attribute :project do
- Project.fabricate! do |resource|
- resource.name = 'project-for-wikis'
- resource.description = 'project for adding wikis'
- end
- end
-
- attribute :repository_http_location do
- Page::Project::Wiki::Show.perform(&:click_clone_repository)
-
- Page::Project::Wiki::GitAccess.perform do |git_access|
- git_access.choose_repository_clone_http
- git_access.repository_location
- end
- end
-
- attribute :repository_ssh_location do
- Page::Project::Wiki::Show.perform(&:click_clone_repository)
-
- Page::Project::Wiki::GitAccess.perform do |git_access|
- git_access.choose_repository_clone_ssh
- git_access.repository_location
- end
- end
-
- def fabricate!
- project.visit!
-
- Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
-
- Page::Project::Wiki::New.perform do |wiki_new|
- wiki_new.click_create_your_first_page_button
- wiki_new.set_title(@title)
- wiki_new.set_content(@content)
- wiki_new.set_message(@message)
- wiki_new.create_new_page
- end
- end
- end
- end
-end
diff --git a/qa/qa/resource/wiki/project_page.rb b/qa/qa/resource/wiki/project_page.rb
new file mode 100644
index 00000000000..5d0a0a37765
--- /dev/null
+++ b/qa/qa/resource/wiki/project_page.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Wiki
+ class ProjectPage < Base
+ attribute :title
+ attribute :content
+ attribute :slug
+ attribute :format
+
+ attribute :project do
+ Project.fabricate_via_api! do |project|
+ project.name = 'wiki_testing'
+ project.description = 'project for testing wikis'
+ end
+ end
+
+ attribute :repository_http_location do
+ switching_to_wiki_url project.repository_http_location.git_uri
+ end
+
+ attribute :repository_ssh_location do
+ switching_to_wiki_url project.repository_ssh_location.git_uri
+ end
+
+ def initialize
+ @title = 'Home'
+ @content = 'This wiki page is created by the API'
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # TODO
+ # workaround
+ project.web_url.concat("/-/wikis/#{slug}")
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/wikis/#{slug}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/wikis"
+ end
+
+ def api_post_body
+ {
+ id: project.id,
+ content: content,
+ title: title
+ }
+ end
+
+ private
+
+ def switching_to_wiki_url(url)
+ # TODO
+ # workaround
+ # i.e. This replaces the last occurence of the string (case sensitive)
+ # and attaches everything before to the new substring
+ Git::Location.new(url.to_s.gsub(/(.*)\bgit\b/i, '\1wiki.git'))
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/api/repository_storage_moves.rb b/qa/qa/runtime/api/repository_storage_moves.rb
new file mode 100644
index 00000000000..c94a693289f
--- /dev/null
+++ b/qa/qa/runtime/api/repository_storage_moves.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module API
+ module RepositoryStorageMoves
+ extend self
+ extend Support::Api
+
+ RepositoryStorageMovesError = Class.new(RuntimeError)
+
+ def has_status?(project, status, destination_storage = Env.additional_repository_storage)
+ all.any? do |move|
+ move[:project][:path_with_namespace] == project.path_with_namespace &&
+ move[:state] == status &&
+ move[:destination_storage_name] == destination_storage
+ end
+ end
+
+ def all
+ Logger.debug('Getting repository storage moves')
+ parse_body(get(Request.new(api_client, '/project_repository_storage_moves').url))
+ end
+
+ private
+
+ def api_client
+ @api_client ||= Client.as_admin
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 5c23586fb3e..677fba7ced7 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -210,6 +210,18 @@ module QA
ENV['GITLAB_QA_1P_GITHUB_UUID']
end
+ def jira_admin_username
+ ENV['JIRA_ADMIN_USERNAME']
+ end
+
+ def jira_admin_password
+ ENV['JIRA_ADMIN_PASSWORD']
+ end
+
+ def jira_hostname
+ ENV['JIRA_HOSTNAME']
+ end
+
def knapsack?
!!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index 5e948b5b850..579c2293c51 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -28,19 +28,11 @@ module QA
end
def enable_and_verify(key)
- Support::Retrier.retry_on_exception(sleep_interval: 2) do
- enable(key)
-
- is_enabled = false
-
- QA::Support::Waiter.wait_until(sleep_interval: 1) do
- is_enabled = enabled?(key)
- end
-
- raise SetFeatureError, "#{key} was not enabled!" unless is_enabled
+ set_and_verify(key, enable: true)
+ end
- QA::Runtime::Logger.info("Successfully enabled and verified feature flag: #{key}")
- end
+ def disable_and_verify(key)
+ set_and_verify(key, enable: false)
end
def enabled?(key)
@@ -75,6 +67,27 @@ module QA
end
end
+ # Change a feature flag and verify that the change was successful
+ # Arguments:
+ # key: The feature flag to set (as a string)
+ # enable: `true` to enable the flag, `false` to disable it
+ def set_and_verify(key, enable:)
+ Support::Retrier.retry_on_exception(sleep_interval: 2) do
+ enable ? enable(key) : disable(key)
+
+ is_enabled = nil
+
+ QA::Support::Waiter.wait_until(sleep_interval: 1) do
+ is_enabled = enabled?(key)
+ is_enabled == enable
+ end
+
+ raise SetFeatureError, "#{key} was not #{enable ? 'enabled' : 'disabled'}!" unless is_enabled == enable
+
+ QA::Runtime::Logger.info("Successfully #{enable ? 'enabled' : 'disabled'} and verified feature flag: #{key}")
+ end
+ end
+
def set_feature(key, value)
request = Runtime::API::Request.new(api_client, "/features/#{key}")
response = post(request.url, { value: value })
diff --git a/qa/qa/scenario/test/instance/airgapped.rb b/qa/qa/scenario/test/instance/airgapped.rb
new file mode 100644
index 00000000000..556741ec040
--- /dev/null
+++ b/qa/qa/scenario/test/instance/airgapped.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Instance
+ class Airgapped < Template
+ include Bootable
+ include SharedAttributes
+ def perform(address, *rspec_options)
+ Runtime::Scenario.define(:runner_network, 'airgapped')
+
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb
index b02bbea8ff5..512960e8232 100644
--- a/qa/qa/service/docker_run/base.rb
+++ b/qa/qa/service/docker_run/base.rb
@@ -8,6 +8,7 @@ module QA
def initialize
@network = Runtime::Scenario.attributes[:network] || 'test'
+ @runner_network = Runtime::Scenario.attributes[:runner_network] || @network
end
def network
@@ -18,6 +19,14 @@ module QA
@network
end
+ def runner_network
+ shell "docker network inspect #{@runner_network}"
+ rescue CommandError
+ network
+ else
+ @runner_network
+ end
+
def pull
shell "docker pull #{@image}"
end
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 834f6b430ac..6022ee4ceab 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'resolv'
require 'securerandom'
module QA
@@ -38,11 +39,16 @@ module QA
def register!
shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh
- --network #{network} --name #{@name}
+ --network #{runner_network} --name #{@name}
#{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker}
--privileged
#{@image} -c "#{register_command}"
CMD
+
+ # Prove airgappedness
+ if runner_network == 'airgapped'
+ shell("docker exec #{@name} sh -c '#{prove_airgap}'")
+ end
end
def tags=(tags)
@@ -85,6 +91,17 @@ module QA
gitlab-runner run
CMD
end
+
+ # Ping CloudFlare DNS, should fail
+ # Ping Registry, should fail to resolve
+ def prove_airgap
+ gitlab_ip = Resolv.getaddress 'registry.gitlab.com'
+ <<~CMD
+ echo "Checking airgapped connectivity..."
+ nc -zv -w 10 #{gitlab_ip} 80 && (echo "Airgapped network faulty. Connectivity netcat check failed." && exit 1) || (echo "Connectivity netcat check passed." && exit 0)
+ wget --retry-connrefused --waitretry=1 --read-timeout=15 --timeout=10 -t 2 http://registry.gitlab.com > /dev/null 2>&1 && (echo "Airgapped network faulty. Connectivity wget check failed." && exit 1) || (echo "Airgapped network confirmed. Connectivity wget check passed." && exit 0)
+ CMD
+ end
end
end
end
diff --git a/qa/qa/service/docker_run/jenkins.rb b/qa/qa/service/docker_run/jenkins.rb
index 00b63282484..808fab80c63 100644
--- a/qa/qa/service/docker_run/jenkins.rb
+++ b/qa/qa/service/docker_run/jenkins.rb
@@ -16,7 +16,9 @@ module QA
end
def host_name
- return 'localhost' unless QA::Runtime::Env.running_in_ci?
+ if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped')
+ 'localhost'
+ end
super
end
@@ -33,7 +35,9 @@ module QA
#{@image}
CMD
- command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci?
+ if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped')
+ command.gsub!("--network #{network} ", '')
+ end
shell command
end
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
new file mode 100644
index 00000000000..d5ab6a3544d
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Changing Gitaly repository storage', :orchestrated, :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')
+
+ project.change_repository_storage(destination_storage)
+
+ expect(Runtime::API::RepositoryStorageMoves).to have_status(project, 'finished', destination_storage)
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = 'new_file'
+ push.file_content = '# This is a new file'
+ push.commit_message = 'Add new file'
+ push.new_branch = false
+ end
+
+ expect(project).to have_file('README.md')
+ expect(project).to have_file('new_file')
+ end
+ end
+
+ context 'when moving from one Gitaly storage to another', :repository_storage do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'repo-storage-move-status'
+ project.initialize_with_readme = true
+ end
+ end
+ let(:destination_storage) { QA::Runtime::Env.additional_repository_storage }
+
+ it_behaves_like 'repository storage move'
+ end
+
+ context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'repo-storage-move'
+ project.initialize_with_readme = true
+ project.repository_storage = 'gitaly'
+ end
+ end
+ let(:destination_storage) { QA::Runtime::Env.praefect_repository_storage }
+
+ it_behaves_like 'repository storage move'
+ end
+ end
+ end
+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 e30afbf8ae0..67055537567 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
@@ -12,8 +12,8 @@ module QA
project.name = 'add-member-project'
end.visit!
- Page::Project::Menu.perform(&:go_to_members_settings)
- Page::Project::Settings::Members.perform do |members|
+ Page::Project::Menu.perform(&:click_members)
+ Page::Project::Members.perform do |members|
members.add_member(user.username)
end
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 7b4418191a3..57b0859856e 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
@@ -3,6 +3,8 @@
module QA
context 'Plan', :smoke do
describe 'Issue creation' do
+ let(:closed_issue) { Resource::Issue.fabricate_via_api! }
+
before do
Flow::Login.sign_in
end
@@ -17,6 +19,25 @@ module QA
end
end
+ it 'closes an issue' do
+ closed_issue.visit!
+
+ Page::Project::Issue::Show.perform do |issue_page|
+ issue_page.click_close_issue_button
+
+ expect(issue_page).to have_element(:reopen_issue_button)
+ end
+
+ Page::Project::Menu.perform(&:click_issues)
+ Page::Project::Issue::Index.perform do |index|
+ expect(index).not_to have_issue(closed_issue)
+
+ index.click_closed_issues_link
+
+ expect(index).to have_issue(closed_issue)
+ end
+ end
+
context 'when using attachments in comments', :object_storage do
let(:gif_file_name) { 'banana_sample.gif' }
let(:file_to_attach) do
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 9b46a066c8e..623573a1397 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
@@ -16,10 +16,10 @@ module QA
it 'shows issue suggestions when creating a new issue' do
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |new_page|
- new_page.add_title("issue")
+ new_page.fill_title("issue")
expect(new_page).to have_content(issue_title)
- new_page.add_title("Issue Board")
+ new_page.fill_title("Issue Board")
expect(new_page).not_to have_content(issue_title)
end
end
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 409c7d321f0..c81a6f9281c 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
@@ -17,7 +17,7 @@ module QA
project.visit!
Page::Project::Menu.perform(&:go_to_boards)
- EE::Page::Component::IssueBoard::Show.perform do |show|
+ Page::Component::IssueBoard::Show.perform do |show|
show.click_focus_mode_button
expect(show.focused_board).to be_visible
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
new file mode 100644
index 00000000000..05a932fd53e
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ include Support::Api
+
+ describe 'Jira integration', :jira, :orchestrated, :requires_admin do
+ let(:jira_project_key) { 'JITP' }
+
+ before(:all) 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
+ 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("Requests to the local network are not allowed")
+ end
+ end
+
+ it 'closes an issue via pushing a commit' do
+ issue_key = Vendor::Jira::JiraAPI.perform do |jira_api|
+ jira_api.create_issue(jira_project_key)
+ end
+
+ push_commit("Closes #{issue_key}")
+
+ expect_issue_done(issue_key)
+ end
+
+ it 'closes an issue via a merge request' do
+ issue_key = Vendor::Jira::JiraAPI.perform do |jira_api|
+ jira_api.create_issue(jira_project_key)
+ end
+
+ page.visit Runtime::Scenario.gitlab_address
+ Flow::Login.sign_in_unless_signed_in
+
+ merge_request = create_mr_with_description("Closes #{issue_key}")
+
+ merge_request.visit!
+
+ Page::MergeRequest::Show.perform(&:merge!)
+
+ expect_issue_done(issue_key)
+ end
+
+ def create_mr_with_description(description)
+ Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.project = @project
+ merge_request.target_new_branch = !master_branch_exists?
+ merge_request.description = description
+ end
+ end
+
+ def push_commit(commit_message)
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.branch_name = 'master'
+ push.commit_message = commit_message
+ push.file_content = commit_message
+ push.project = @project
+ push.new_branch = !master_branch_exists?
+ end
+ end
+
+ def expect_issue_done(issue_key)
+ expect do
+ Support::Waiter.wait_until(raise_on_failure: true) do
+ jira_issue = Vendor::Jira::JiraAPI.perform do |jira_api|
+ jira_api.fetch_issue(issue_key)
+ end
+
+ jira_issue[:fields][:status][:name] == 'Done'
+ end
+ end.not_to raise_error
+ end
+
+ def master_branch_exists?
+ @project.repository_branches.map { |item| item[:name] }.include?("master")
+ end
+ end
+ end
+end
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 9b504ad76b4..21ae10774c9 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
@@ -3,6 +3,12 @@
module QA
context '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
+ # triggering the limit set in this test (which can happen on Staging where the
+ # tests are run in parallel).
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/218620#note_361634705
+
include Support::Api
before(:context) do
@@ -31,7 +37,7 @@ module QA
end
it 'push fails when the file size is above the limit' do
- set_file_size_limit(1)
+ set_file_size_limit(2)
retry_on_fail do
expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
@@ -52,7 +58,7 @@ module QA
output = Resource::Repository::Push.fabricate! do |p|
p.repository_http_uri = @project.repository_http_location.uri
p.file_name = file_name
- p.file_content = SecureRandom.random_bytes(2000000)
+ p.file_content = SecureRandom.random_bytes(3000000)
p.commit_message = commit_message
p.new_branch = false
end
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
new file mode 100644
index 00000000000..a3011550db8
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Version control for project snippets' do
+ let(:new_file) { 'new_snippet_file' }
+ let(:changed_content) { 'changes' }
+ let(:commit_message) { 'Changes to snippets' }
+ let(:added_content) { 'updated ' }
+ let(:branch_name) { 'master' }
+
+ let(:snippet) do
+ Resource::ProjectSnippet.fabricate! do |snippet|
+ snippet.file_name = new_file
+ end
+ end
+
+ let(:ssh_key) do
+ Resource::SSHKey.fabricate_via_api! do |resource|
+ resource.title = "my key title #{Time.now.to_f}"
+ end
+ end
+
+ let(:repository_uri_http) do
+ snippet
+ Page::Dashboard::Snippet::Show.perform(&:get_repository_uri_http)
+ end
+
+ let(:repository_uri_ssh) do
+ ssh_key
+ snippet
+ Page::Dashboard::Snippet::Show.perform(&:get_repository_uri_ssh)
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'clones, pushes, and pulls a project snippet over HTTP, edits via UI' do
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = repository_uri_http
+ push.file_name = new_file
+ push.file_content = changed_content
+ push.commit_message = commit_message
+ push.new_branch = false
+ end
+
+ page.refresh
+ verify_changes_in_ui
+
+ Page::Dashboard::Snippet::Show.perform(&:click_edit_button)
+
+ Page::Dashboard::Snippet::Edit.perform do |snippet|
+ snippet.add_to_file_content(added_content)
+ snippet.save_changes
+ end
+
+ Git::Repository.perform do |repository|
+ repository.init_repository
+ repository.pull(repository_uri_http, branch_name)
+
+ expect(repository.commits.size).to eq 3
+ expect(repository.commits.first).to include 'Update snippet'
+ expect(repository.file_content(new_file)).to include "#{added_content}#{changed_content}"
+ end
+ end
+
+ it 'clones, pushes, and pulls a project snippet over SSH, deletes via UI' do
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_ssh_uri = repository_uri_ssh
+ push.ssh_key = ssh_key
+ push.file_name = new_file
+ push.file_content = changed_content
+ push.commit_message = commit_message
+ push.new_branch = false
+ end
+
+ page.refresh
+ verify_changes_in_ui
+ Page::Dashboard::Snippet::Show.perform(&:click_delete_button)
+
+ # attempt to pull a deleted snippet, get a missing repository error
+ Git::Repository.perform do |repository|
+ repository.uri = repository_uri_ssh
+ repository.use_ssh_key(ssh_key)
+ repository.init_repository
+
+ expect { repository.pull(repository_uri_ssh, branch_name) }
+ .to raise_error(QA::Git::Repository::RepositoryCommandError, /[fatal: Could not read from remote repository.]+/)
+ end
+ end
+
+ def verify_changes_in_ui
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ expect(snippet).to have_file_name(new_file)
+ expect(snippet).to have_file_content(changed_content)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
index dfcbf4b44c8..451a7847f8b 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
@@ -2,8 +2,8 @@
module QA
context 'Create', :smoke do
- describe 'Snippet creation' do
- it 'User creates a snippet' do
+ describe 'Personal snippet creation' do
+ it 'User creates a personal snippet' do
Flow::Login.sign_in
Page::Main::Menu.perform(&:go_to_snippets)
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
new file mode 100644
index 00000000000..8fc4427bda7
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ context '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
+
+ Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
+ snippet.title = 'Project snippet'
+ snippet.description = ' '
+ snippet.visibility = 'Private'
+ snippet.file_name = 'markdown_file.md'
+ snippet.file_content = "### Snippet heading\n\n[Gitlab link](https://gitlab.com/)"
+ end
+
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ expect(snippet).to have_snippet_title('Project snippet')
+ expect(snippet).to have_no_snippet_description
+ expect(snippet).to have_visibility_type(/private/i)
+ expect(snippet).to have_file_name('markdown_file.md')
+ expect(snippet).to have_file_content('Snippet heading')
+ expect(snippet).to have_file_content('Gitlab link')
+ expect(snippet).not_to have_file_content('###')
+ expect(snippet).not_to have_file_content('https://gitlab.com/')
+ end
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..3bf6e156967
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'First file using Web IDE' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'empty-project'
+ project.initialize_with_readme = false
+ end
+ end
+
+ let(:web_ide_url) { current_url + '-/ide/project/' + project.path_with_namespace }
+ let(:file_name) { 'the very first file.txt' }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it "creates the first file in an empty project via Web IDE" do
+ # In the first iteration, the test opens Web IDE by modifying the URL to address past regressions.
+ # Once the Web IDE button is introduced for empty projects, the test will be modified to go through UI.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/27915 and https://gitlab.com/gitlab-org/gitlab/-/issues/27535.
+ page.visit(web_ide_url)
+
+ Page::Project::WebIDE::Edit.perform do |ide|
+ ide.create_first_file(file_name)
+ ide.commit_changes
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project|
+ expect(project).to have_file(file_name)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
deleted file mode 100644
index 185d10a64ed..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Create' do
- describe 'Wiki management' do
- it 'user creates, edits, clones, and pushes to the wiki' do
- Flow::Login.sign_in
-
- wiki = Resource::Wiki.fabricate_via_browser_ui! do |resource|
- resource.title = 'Home'
- resource.content = '# My First Wiki Content'
- resource.message = 'Update home'
- end
-
- validate_content('My First Wiki Content')
-
- Page::Project::Wiki::Edit.perform(&:click_edit)
- Page::Project::Wiki::New.perform do |wiki|
- wiki.set_content("My Second Wiki Content")
- wiki.save_changes
- end
-
- validate_content('My Second Wiki Content')
-
- Resource::Repository::WikiPush.fabricate! do |push|
- push.wiki = wiki
- push.file_name = 'Home.md'
- push.file_content = '# My Third Wiki Content'
- push.commit_message = 'Update Home.md'
- end
- Page::Project::Menu.perform(&:click_wiki)
-
- expect(page).to have_content('My Third Wiki Content')
- end
-
- def validate_content(content)
- expect(page).to have_content('Wiki was successfully updated')
- expect(page).to have_content(/#{content}/)
- end
- end
- end
-end
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
new file mode 100644
index 00000000000..1d0c8ee60d4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ context 'Wiki' do
+ describe 'testing wiki content creation inside a project' do
+ let(:new_wiki_title) { "just_another_wiki_page" }
+ let(:new_wiki_content) { "this content is changed or added" }
+ let(:commit_message) { "this is a new addition to the wiki" }
+
+ let(:project) { Resource::Project.fabricate_via_api! }
+ let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'by adding a home page to the wiki' do
+ project.visit!
+
+ Page::Project::Menu.perform(&:click_wiki)
+ Page::Project::Wiki::Show.perform(&:click_create_your_first_page)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_create_page)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a second page to the wiki' do
+ wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_new_page)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_create_page)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a home page to the wiki using git push' do
+ empty_wiki = Resource::Wiki::ProjectPage.new do |empty_wiki|
+ empty_wiki.project = project
+ end
+
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_name = "#{new_wiki_title}.md"
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = empty_wiki
+ push.new_branch = true
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by adding a second page to the wiki using git push' do
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_name = "#{new_wiki_title}.md"
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = wiki
+ push.new_branch = false
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..10370c80476
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ context 'Wiki' do
+ describe 'testing wiki content manipulation inside a project' do
+ let(:new_wiki_title) { "just_another_wiki_page" }
+ let(:new_wiki_content) { "this content is changed or added" }
+ let(:commit_message) { "this is a new addition to the wiki" }
+
+ let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'by manipulating content on the page' do
+ wiki.visit!
+
+ Page::Project::Wiki::Show.perform(&:click_edit)
+
+ Page::Project::Wiki::Edit.perform do |edit|
+ edit.set_title new_wiki_title
+ edit.set_content new_wiki_content
+ edit.set_message commit_message
+ end
+
+ Page::Project::Wiki::Edit.perform(&:click_save_changes)
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_title new_wiki_title
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+
+ it 'by manipulating content on the page using git push' do
+ Resource::Repository::WikiPush.fabricate! do |push|
+ push.file_content = new_wiki_content
+ push.commit_message = commit_message
+ push.wiki = wiki
+ push.new_branch = false
+ end.visit!
+
+ Page::Project::Wiki::Show.perform do |wiki|
+ expect(wiki).to have_content new_wiki_content
+ end
+ end
+ end
+ end
+ end
+end
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 b1eb26f0d63..e1d8c50ab75 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
@@ -29,10 +29,12 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
- parent_pipeline.click_linked_job(project.name)
+ 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")
- expect(parent_pipeline).to have_passed
end
end
@@ -41,10 +43,12 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
- parent_pipeline.click_linked_job(project.name)
+ 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")
- expect(parent_pipeline).to have_failed
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 c9a61fc6305..c365e084991 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
@@ -29,10 +29,12 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
- parent_pipeline.click_linked_job(project.name)
+ 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")
- expect(parent_pipeline).to have_passed
end
end
@@ -41,10 +43,12 @@ module QA
view_pipelines
Page::Project::Pipeline::Show.perform do |parent_pipeline|
- parent_pipeline.click_linked_job(project.name)
+ 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")
- expect(parent_pipeline).to have_passed
end
end
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 04c68598239..0e9c369e97f 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,10 +1,10 @@
# frozen_string_literal: true
module QA
- context 'Configure', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/209085', type: :investigating } do
- describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin do
+ context 'Configure' do
+ describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env do
context 'Project Clusters' do
- let(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
+ let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-k8s'
@@ -13,7 +13,7 @@ module QA
end
before do
- Flow::Login.sign_in
+ Flow::Login.sign_in_as_admin
end
after 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 45273655bb6..a9ed6651069 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,11 +1,16 @@
# frozen_string_literal: true
module QA
- context 'Monitor', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217705', type: :flaky } do
- describe 'with Prometheus Gitlab-managed cluster', :orchestrated, :kubernetes, :docker, :runner do
+ context 'Monitor' do
+ describe 'with Prometheus in a Gitlab-managed cluster', :orchestrated, :kubernetes do
before :all do
- Flow::Login.sign_in
- @project, @runner = deploy_project_with_prometheus
+ @cluster = Service::KubernetesCluster.new.create!
+ @project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'monitoring-project'
+ project.auto_devops_enabled = true
+ end
+
+ deploy_project_with_prometheus
end
before do
@@ -14,7 +19,6 @@ module QA
end
after :all do
- @runner.remove_via_api!
@cluster.remove!
end
@@ -27,81 +31,94 @@ module QA
it 'duplicates to create dashboard to custom' do
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- dashboard.duplicate_dashboard
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ on_dashboard.duplicate_dashboard
- expect(dashboard).to have_metrics
- expect(dashboard).to have_edit_dashboard_enabled
+ expect(on_dashboard).to have_metrics
+ expect(on_dashboard).to have_edit_dashboard_enabled
end
end
it 'verifies data on filtered deployed environment' do
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- dashboard.filter_environment
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ on_dashboard.filter_environment
- expect(dashboard).to have_metrics
+ expect(on_dashboard).to have_metrics
end
end
it 'filters using the quick range' do
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- dashboard.show_last('30 minutes')
- expect(dashboard).to have_metrics
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ on_dashboard.show_last('30 minutes')
+ expect(on_dashboard).to have_metrics
- dashboard.show_last('3 hours')
- expect(dashboard).to have_metrics
+ on_dashboard.show_last('3 hours')
+ expect(on_dashboard).to have_metrics
- dashboard.show_last('1 day')
- expect(dashboard).to have_metrics
+ on_dashboard.show_last('1 day')
+ expect(on_dashboard).to have_metrics
end
end
private
def deploy_project_with_prometheus
- project = Resource::Project.fabricate_via_api! do |project|
- project.name = 'cluster-with-prometheus'
- project.description = 'Cluster with Prometheus'
+ %w[
+ CODE_QUALITY_DISABLED TEST_DISABLED LICENSE_MANAGEMENT_DISABLED
+ SAST_DISABLED DAST_DISABLED DEPENDENCY_SCANNING_DISABLED
+ CONTAINER_SCANNING_DISABLED PERFORMANCE_DISABLED
+ ].each do |key|
+ Resource::CiVariable.fabricate_via_api! do |resource|
+ resource.project = @project
+ resource.key = key
+ resource.value = '1'
+ resource.masked = false
+ end
end
- runner = Resource::Runner.fabricate_via_api! do |runner|
- runner.project = project
- runner.name = project.name
- end
-
- @cluster = Service::KubernetesCluster.new.create!
+ Flow::Login.sign_in
- cluster_props = Resource::KubernetesCluster::ProjectCluster.fabricate! do |cluster_settings|
- cluster_settings.project = project
+ Resource::KubernetesCluster::ProjectCluster.fabricate! do |cluster_settings|
+ cluster_settings.project = @project
cluster_settings.cluster = @cluster
cluster_settings.install_helm_tiller = true
+ cluster_settings.install_runner = true
cluster_settings.install_ingress = true
cluster_settings.install_prometheus = true
end
- Resource::CiVariable.fabricate_via_api! do |ci_variable|
- ci_variable.project = project
- ci_variable.key = 'AUTO_DEVOPS_DOMAIN'
- ci_variable.value = cluster_props.ingress_ip
- ci_variable.masked = false
- end
-
Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
+ push.project = @project
push.directory = Pathname
.new(__dir__)
- .join('../../../../fixtures/monitored_auto_devops')
+ .join('../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create AutoDevOps compatible Project for Monitoring'
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success_or_retry)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('build')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 600)
+
+ job.click_element(:pipeline_path)
+ end
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('production')
+ end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 1200)
- [project, runner]
+ job.click_element(:pipeline_path)
+ end
end
def verify_add_custom_metric
@@ -115,8 +132,8 @@ module QA
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- expect(dashboard).to have_custom_metric('HTTP Requests Total')
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ expect(on_dashboard).to have_custom_metric('HTTP Requests Total')
end
end
@@ -130,8 +147,8 @@ module QA
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- expect(dashboard).to have_custom_metric('Throughput')
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ expect(on_dashboard).to have_custom_metric('Throughput')
end
end
@@ -146,8 +163,8 @@ module QA
Page::Project::Menu.perform(&:go_to_operations_metrics)
- Page::Project::Operations::Metrics::Show.perform do |dashboard|
- expect(dashboard).not_to have_custom_metric('Throughput')
+ Page::Project::Operations::Metrics::Show.perform do |on_dashboard|
+ expect(on_dashboard).not_to have_custom_metric('Throughput')
end
end
end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index f5e4d4e294b..3c46c039eae 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -11,22 +11,31 @@ module QA
HTTP_STATUS_ACCEPTED = 202
HTTP_STATUS_SERVER_ERROR = 500
- def post(url, payload)
- RestClient::Request.execute(
+ def post(url, payload, args = {})
+ default_args = {
method: :post,
url: url,
payload: payload,
- verify_ssl: false)
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(
+ default_args.merge(args)
+ )
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
- def get(url, raw_response: false)
- RestClient::Request.execute(
+ def get(url, args = {})
+ default_args = {
method: :get,
url: url,
- verify_ssl: false,
- raw_response: raw_response)
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(
+ default_args.merge(args)
+ )
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb
index 14771243beb..6f8c4a59566 100644
--- a/qa/qa/support/repeater.rb
+++ b/qa/qa/support/repeater.rb
@@ -7,8 +7,9 @@ module QA
module Repeater
DEFAULT_MAX_WAIT_TIME = 60
- RetriesExceededError = Class.new(RuntimeError)
- WaitExceededError = Class.new(RuntimeError)
+ RepeaterConditionExceededError = Class.new(RuntimeError)
+ RetriesExceededError = Class.new(RepeaterConditionExceededError)
+ WaitExceededError = Class.new(RepeaterConditionExceededError)
def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
attempts = 0
diff --git a/qa/qa/vendor/jira/jira_api.rb b/qa/qa/vendor/jira/jira_api.rb
new file mode 100644
index 00000000000..65b080df3d0
--- /dev/null
+++ b/qa/qa/vendor/jira/jira_api.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Jira
+ class JiraAPI
+ include Scenario::Actable
+ include Support::Api
+
+ def base_url
+ host = QA::Runtime::Env.jira_hostname || 'localhost'
+
+ "http://#{host}:8080"
+ end
+
+ def api_url
+ "#{base_url}/rest/api/2"
+ end
+
+ def fetch_issue(issue_key)
+ response = get("#{api_url}/issue/#{issue_key}", user: Runtime::Env.jira_admin_username, password: Runtime::Env.jira_admin_password)
+
+ parse_body(response)
+ end
+
+ def create_issue(jira_project_key)
+ payload = {
+ fields: {
+ project: {
+ key: jira_project_key
+ },
+ summary: 'REST ye merry gentlemen.',
+ description: 'Creating of an issue using project keys and issue type names using the REST API',
+ issuetype: {
+ name: 'Bug'
+ }
+ }
+ }
+
+ response = post("#{api_url}/issue",
+ payload.to_json, headers: { 'Content-Type': 'application/json' },
+ user: Runtime::Env.jira_admin_username,
+ password: Runtime::Env.jira_admin_password)
+
+ issue_key = parse_body(response)[:key]
+
+ QA::Runtime::Logger.debug("Created JIRA issue with key: '#{issue_key}'")
+
+ issue_key
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
index 94638d99b01..db3c2f65963 100644
--- a/qa/spec/runtime/feature_spec.rb
+++ b/qa/spec/runtime/feature_spec.rb
@@ -25,6 +25,21 @@ describe QA::Runtime::Feature do
end
end
+ describe '.enable_and_verify' do
+ it 'enables a feature flag' do
+ allow(described_class).to receive(:get).and_return(response_get)
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a-flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: true }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ subject.enable_and_verify('a-flag')
+ end
+ end
+
describe '.disable' do
it 'disables a feature flag' do
expect(QA::Runtime::API::Request)
@@ -40,6 +55,22 @@ describe QA::Runtime::Feature do
end
end
+ describe '.disable_and_verify' do
+ it 'disables a feature flag' do
+ allow(described_class).to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "off" }]'))
+
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features/a-flag").and_return(request)
+ expect(described_class).to receive(:post)
+ .with(request.url, { value: false }).and_return(response_post)
+ expect(QA::Runtime::API::Request).to receive(:new)
+ .with(api_client, "/features").and_return(request)
+
+ subject.disable_and_verify('a-flag')
+ end
+ end
+
describe '.enabled?' do
it 'returns a feature flag state' do
expect(QA::Runtime::API::Request)
diff --git a/qa/spec/scenario/test/instance/airgapped_spec.rb b/qa/spec/scenario/test/instance/airgapped_spec.rb
new file mode 100644
index 00000000000..0c4167eafff
--- /dev/null
+++ b/qa/spec/scenario/test/instance/airgapped_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+describe QA::Scenario::Test::Instance::Airgapped do
+ describe '#perform' do
+ it_behaves_like 'a QA scenario class' do
+ end
+ end
+end
diff --git a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb
new file mode 100644
index 00000000000..feaeb78815d
--- /dev/null
+++ b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module QA
+ shared_examples 'code owner merge request' do
+ let(:branch_name) { 'new-branch' }
+
+ it 'is approved and merged' do
+ # Require one approval from any eligible user on any branch
+ # This will confirm that this type of unrestricted approval is
+ # also satisfied when a code owner grants approval
+ Page::Project::Menu.perform(&:go_to_general_settings)
+ Page::Project::Settings::Main.perform do |main|
+ main.expand_merge_request_approvals_settings do |settings|
+ settings.set_default_number_of_approvals_required(1)
+ end
+ end
+
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add CODEOWNERS'
+ commit.add_files(
+ [
+ {
+ file_path: 'CODEOWNERS',
+ content: <<~CONTENT
+ README.md @#{codeowner}
+ CONTENT
+ }
+ ]
+ )
+ end
+
+ # Require approval from code owners on master
+ Resource::ProtectedBranch.fabricate! do |protected_branch|
+ protected_branch.project = project
+ protected_branch.branch_name = 'master'
+ protected_branch.new_branch = false
+ protected_branch.require_code_owner_approval = true
+ end
+
+ # Push a change to the file with a CODEOWNERS rule
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = project.repository_http_location.uri
+ push.branch_name = branch_name
+ push.file_name = 'README.md'
+ push.file_content = 'Updated'
+ end
+
+ merge_request = Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.project = project
+ merge_request.target_new_branch = false
+ merge_request.source_branch = branch_name
+ merge_request.no_preparation = true
+ end
+
+ Flow::Login.while_signed_in(as: approver) do
+ merge_request.visit!
+
+ Page::MergeRequest::Show.perform do |merge_request|
+ expect(merge_request.approvals_required_from).to include('Code Owners')
+ expect(merge_request).not_to be_mergeable
+
+ merge_request.click_approve
+ merge_request.merge!
+
+ expect(merge_request).to be_merged
+ end
+ end
+ end
+ end
+end