Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-13 18:50:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-13 18:50:28 +0300
commita21e41126b4430cfdde3ba19fcf29eb1b945967b (patch)
tree93b355debe75023189f0baaf07a5167c14742115 /qa
parent96b52080893d23a79c29a9836762b9c135111cc4 (diff)
Add latest changes from gitlab-org/gitlab@12-0-stable-ee
Diffstat (limited to 'qa')
-rw-r--r--qa/qa/page/admin/menu.rb2
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/file/show.rb2
-rw-r--r--qa/qa/page/main/login.rb31
-rw-r--r--qa/qa/page/merge_request/show.rb7
-rw-r--r--qa/qa/page/profile/menu.rb2
-rw-r--r--qa/qa/page/project/issue/index.rb2
-rw-r--r--qa/qa/page/project/issue/new.rb12
-rw-r--r--qa/qa/page/project/issue/show.rb15
-rw-r--r--qa/qa/page/project/new.rb1
-rw-r--r--qa/qa/page/project/pipeline/show.rb2
-rw-r--r--qa/qa/page/project/settings/mirroring_repositories.rb2
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb2
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/omnibus.rb11
-rw-r--r--qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb172
-rw-r--r--qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb91
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb184
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb138
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb28
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb135
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb85
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb48
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb40
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb72
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb60
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb76
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb74
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb137
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb178
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb82
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb166
-rw-r--r--qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb206
-rw-r--r--qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb63
-rw-r--r--qa/qa/vendor/saml_idp/page/login.rb8
-rw-r--r--qa/spec/resource/repository/push_spec.rb6
-rw-r--r--qa/spec/runtime/env_spec.rb24
39 files changed, 2141 insertions, 31 deletions
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index eea5717f5a7..8d5c30bfdcf 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Admin
class Menu < Page::Base
+ prepend EE::Page::Admin::Menu # rubocop: disable Cop/InjectEnterpriseEditionModule
+
view 'app/views/layouts/nav/sidebar/_admin.html.haml' do
element :admin_sidebar
element :admin_sidebar_submenu
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 0c23d7cffbb..81c2a8c33f4 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Dashboard
class Projects < Page::Base
+ prepend QA::EE::Page::Dashboard::Projects
+
view 'app/views/shared/projects/_search_form.html.haml' do
element :project_filter_form, required: true
end
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index eaf88c6e69e..9af416d2de7 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -4,6 +4,8 @@ module QA
module Page
module File
class Show < Page::Base
+ prepend QA::EE::Page::File::Show
+
include Shared::CommitMessage
view 'app/helpers/blob_helper.rb' do
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 8970eeb6678..8d18b32b52f 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -52,13 +52,11 @@ module QA
raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given?
if Runtime::User.ldap_user?
- sign_in_using_ldap_credentials
+ sign_in_using_ldap_credentials(user || Runtime::User)
else
sign_in_using_gitlab_credentials(user || Runtime::User)
end
end
-
- Page::Main::Menu.act { has_personal_area? }
end
def sign_in_using_admin_credentials
@@ -73,6 +71,25 @@ module QA
sign_in_using_gitlab_credentials(admin)
end
+ Page::Main::Menu.perform(&:has_personal_area?)
+ end
+
+ def sign_in_using_ldap_credentials(user)
+ # Log out if already logged in
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+
+ using_wait_time 0 do
+ set_initial_password_if_present
+
+ switch_to_ldap_tab
+
+ fill_element :username_field, user.ldap_username
+ fill_element :password_field, user.ldap_password
+ click_element :sign_in_button
+ end
+
Page::Main::Menu.act { has_personal_area? }
end
@@ -133,14 +150,6 @@ module QA
private
- def sign_in_using_ldap_credentials
- switch_to_ldap_tab
-
- fill_element :username_field, Runtime::User.ldap_username
- fill_element :password_field, Runtime::User.ldap_password
- click_element :sign_in_button
- end
-
def sign_in_using_gitlab_credentials(user)
switch_to_sign_in_tab if has_sign_in_tab?
switch_to_standard_tab if has_standard_tab?
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 6a415b56e50..97c2c616299 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -4,6 +4,7 @@ module QA
module Page
module MergeRequest
class Show < Page::Base
+ prepend QA::EE::Page::MergeRequest::Show
include Page::Component::Note
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
@@ -174,6 +175,12 @@ module QA
click_element :edit_button
end
+ def approvers
+ within_element :approver_list do
+ all_elements(:approver).map(&:text)
+ end
+ end
+
def view_email_patches
click_element :dropdown_toggle
visit_link_in_element(:download_email_patches)
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index 2d503499e13..3386cd6be9d 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Profile
class Menu < Page::Base
+ prepend QA::EE::Page::Profile::Menu
+
view 'app/views/layouts/nav/sidebar/_profile.html.haml' do
element :access_token_link, 'link_to profile_personal_access_tokens_path' # rubocop:disable QA/ElementWithPattern
element :access_token_title, 'Access Tokens' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index c4383951ec4..bf06d146865 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -5,6 +5,8 @@ module QA
module Project
module Issue
class Index < Page::Base
+ prepend QA::EE::Page::Project::Issue::Index
+
view 'app/views/projects/issues/_issue.html.haml' do
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 0d138417176..6ff02b771db 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -5,18 +5,18 @@ module QA
module Project
module Issue
class New < Page::Base
+ 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
+
view 'app/views/shared/issuable/_form.html.haml' do
- element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern
+ 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
@@ -26,7 +26,7 @@ module QA
end
def create_new_issue
- click_on 'Submit issue'
+ click_element :issuable_create_button, Page::Project::Issue::Show
end
end
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index b59540d0377..805805bc869 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -5,6 +5,7 @@ module QA
module Project
module Issue
class Show < Page::Base
+ prepend QA::EE::Page::Project::Issue::Show
include Page::Component::Issuable::Common
include Page::Component::Note
@@ -14,7 +15,7 @@ module QA
end
view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
- element :discussion_filter
+ element :discussion_filter, required: true
element :filter_options
end
@@ -26,17 +27,17 @@ module QA
element :dropdown_input_field
end
+ view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :dropdown_menu_labels
+ element :edit_link_labels
+ element :labels_block
+ end
+
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :labels_block
- element :edit_link_labels
- element :dropdown_menu_labels
- end
-
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil, filter: :all_activities)
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index defd85a5740..a8b9fa56996 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -5,6 +5,7 @@ module QA
module Project
class New < Page::Base
include Page::Component::Select2
+ prepend EE::Page::Project::New # rubocop: disable Cop/InjectEnterpriseEditionModule
view 'app/views/projects/new.html.haml' do
element :project_create_from_template_tab
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 284d0957eb8..cb82b650c57 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -3,6 +3,8 @@
module QA::Page
module Project::Pipeline
class Show < QA::Page::Base
+ prepend QA::EE::Page::Project::Pipeline::Show
+
view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb
index 831166f6373..fa915851cca 100644
--- a/qa/qa/page/project/settings/mirroring_repositories.rb
+++ b/qa/qa/page/project/settings/mirroring_repositories.rb
@@ -5,6 +5,8 @@ module QA
module Project
module Settings
class MirroringRepositories < Page::Base
+ prepend EE::Page::Project::Settings::MirroringRepositories # rubocop: disable Cop/InjectEnterpriseEditionModule
+
view 'app/views/projects/mirrors/_authentication_method.html.haml' do
element :authentication_method
element :password
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 903b0979614..ee8bc223044 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -5,6 +5,8 @@ module QA
module Project
module Settings
class ProtectedBranches < Page::Base
+ prepend EE::Page::Project::Settings::ProtectedBranches # rubocop: disable Cop/InjectEnterpriseEditionModule
+
view 'app/views/projects/protected_branches/shared/_dropdown.html.haml' do
element :protected_branch_select
element :protected_branch_dropdown
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 1a9a2fd413f..3b8493c0a7a 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Project
class Show < Page::Base
+ prepend QA::EE::Page::Project::Show
+
include Page::Component::ClonePanel
view 'app/views/layouts/header/_new_dropdown.haml' do
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 96f337dc081..03904aed599 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -3,6 +3,8 @@
module QA
module Runtime
module Env
+ prepend QA::EE::Runtime::Env
+
extend self
attr_writer :personal_access_token, :ldap_username, :ldap_password
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index e05269e8d55..dcd7aab8b10 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -7,7 +7,7 @@ module QA
class Selectors < Scenario::Template
include Scenario::Bootable
- PAGES = [QA::Page].freeze
+ PAGES = [QA::Page, QA::EE::Page].freeze
def perform(*)
validators = PAGES.map do |pages|
diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb
index b54fd5628f2..c5cddff56cd 100644
--- a/qa/qa/service/omnibus.rb
+++ b/qa/qa/service/omnibus.rb
@@ -11,11 +11,12 @@ module QA
end
def gitlab_ctl(command, input: nil)
- if input.nil?
- shell "docker exec #{@name} gitlab-ctl #{command}"
- else
- shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'"
- end
+ docker_exec("gitlab-ctl #{command}", input: input)
+ end
+
+ def docker_exec(command, input: nil)
+ command = "#{input} | #{command}" if input
+ shell "docker exec #{@name} bash -c '#{command}'"
end
end
end
diff --git a/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb b/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb
new file mode 100644
index 00000000000..ae30478f60d
--- /dev/null
+++ b/qa/qa/specs/features/api/2_plan/ee_epics_milestone_dates_spec.rb
@@ -0,0 +1,172 @@
+# frozen_string_literal: true
+
+module QA
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/48
+ context 'Plan', :quarantine do
+ describe 'Epics milestone dates API' do
+ before(:context) do
+ @api_client = Runtime::API::Client.new(:gitlab)
+ @group_id = Resource::Group.fabricate_via_api!.id
+ @project_id = create_project
+ @milestone_start_date = (Date.today.to_date + 100).strftime("%Y-%m-%d")
+ @milestone_due_date = (Date.today.to_date + 120).strftime("%Y-%m-%d")
+ @fixed_start_date = Date.today.to_date.strftime("%Y-%m-%d")
+ @fixed_due_date = (Date.today.to_date + 90).strftime("%Y-%m-%d")
+ end
+
+ def create_epic_issue_milestone
+ epic_iid = create_epic
+ milestone_id = create_milestone(@milestone_start_date, @milestone_due_date)
+ issue_id = create_issue(milestone_id)
+ add_issue_to_epic(epic_iid, issue_id)
+ use_epics_milestone_dates(epic_iid)
+ [epic_iid, milestone_id]
+ end
+
+ def create_request(api_endpoint)
+ Runtime::API::Request.new(@api_client, api_endpoint)
+ end
+
+ def create_project
+ project_name = "project_#{SecureRandom.hex(8)}"
+ create_project_request = create_request('/projects')
+ post create_project_request.url, path: project_name, name: project_name, namespace_id: @group_id
+ expect_status(201)
+ json_body[:id]
+ end
+
+ def create_issue(milestone_id)
+ request = create_request("/projects/#{@project_id}/issues")
+ post request.url, title: 'My Test Issue', milestone_id: milestone_id
+ expect_status(201)
+ json_body[:id]
+ end
+
+ def create_milestone(start_date, due_date)
+ request = create_request("/projects/#{@project_id}/milestones")
+ post request.url, title: "Test_Milestone_#{SecureRandom.hex(8)}", due_date: due_date, start_date: start_date
+ expect_status(201)
+ json_body[:id]
+ end
+
+ def create_epic
+ request = create_request("/groups/#{@group_id}/epics")
+ post request.url, title: 'My New Epic', due_date_fixed: @fixed_due_date, start_date_fixed: @fixed_start_date, start_date_is_fixed: true, due_date_is_fixed: true
+ expect_status(201)
+ json_body[:iid]
+ end
+
+ def add_issue_to_epic(epic_iid, issue_id)
+ # Add Issue with milestone to an epic
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{issue_id}")
+ post request.url
+
+ expect_status(201)
+ expect_json('epic.title', 'My New Epic*')
+ expect_json('issue.title', 'My Test Issue')
+ end
+
+ def use_epics_milestone_dates(epic_iid)
+ # Update Epic to use Milestone Dates
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}")
+ put request.url, start_date_is_fixed: false, due_date_is_fixed: false
+
+ expect_status(200)
+ expect_json('start_date_from_milestones', @milestone_start_date)
+ expect_json('due_date_from_milestones', @milestone_due_date)
+ expect_json('due_date_fixed', @fixed_due_date)
+ expect_json('start_date_fixed', @fixed_start_date)
+ expect_json('start_date', @milestone_start_date)
+ expect_json('due_date', @milestone_due_date)
+ end
+
+ it 'Updating milestones changes epic dates' do
+ epic_iid, milestone_id = create_epic_issue_milestone
+ milestone_start_date = Date.today.to_date.strftime("%Y-%m-%d")
+ milestone_due_date = (Date.today.to_date + 30).strftime("%Y-%m-%d")
+
+ # Update Milestone to different dates and see it reflecting in the epics
+ request = create_request("/projects/#{@project_id}/milestones/#{milestone_id}")
+ put request.url, start_date: milestone_start_date, due_date: milestone_due_date
+ expect_status(200)
+
+ # Get Epic Details
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}")
+ get request.url
+ expect_status(200)
+
+ expect_json('start_date_from_milestones', milestone_start_date)
+ expect_json('due_date_from_milestones', milestone_due_date)
+ expect_json('start_date', milestone_start_date)
+ expect_json('due_date', milestone_due_date)
+ end
+
+ it 'Adding another issue updates epic dates' do
+ epic_iid = create_epic_issue_milestone[0]
+ milestone_start_date = Date.today.to_date.strftime("%Y-%m-%d")
+ milestone_due_date = (Date.today.to_date + 150).strftime("%Y-%m-%d")
+
+ # Add another Issue and milestone
+ second_milestone_id = create_milestone(milestone_start_date, milestone_due_date)
+ second_issue_id = create_issue(second_milestone_id)
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{second_issue_id}")
+ post request.url
+ expect_status(201)
+
+ # and check milestone dates
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}")
+ get request.url
+ expect_status(200)
+
+ expect_json('start_date_from_milestones', milestone_start_date)
+ expect_json('due_date_from_milestones', milestone_due_date)
+ expect_json('start_date', milestone_start_date)
+ expect_json('due_date', milestone_due_date)
+ end
+
+ it 'Removing issue updates epic dates' do
+ epic_iid = create_epic_issue_milestone[0]
+
+ # Get epic_issue_id
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues")
+ get request.url
+ expect_status(200)
+ epic_issue_id = json_body[0][:epic_issue_id]
+
+ # Remove Issue
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}/issues/#{epic_issue_id}")
+ delete request.url
+ expect_status(200)
+
+ # and check milestone dates
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}")
+ get request.url
+ expect_status(200)
+
+ expect_json('start_date_from_milestones', nil)
+ expect_json('due_date_from_milestones', nil)
+ expect_json('start_date', nil)
+ expect_json('due_date', nil)
+ end
+
+ it 'Deleting milestones updates epic dates' do
+ epic_iid, milestone_id = create_epic_issue_milestone
+
+ # Delete Milestone
+ request = create_request("/projects/#{@project_id}/milestones/#{milestone_id}")
+ delete request.url
+ expect_status(204)
+
+ # and check milestone dates
+ request = create_request("/groups/#{@group_id}/epics/#{epic_iid}")
+ get request.url
+ expect_status(200)
+
+ expect_json('start_date_from_milestones', nil)
+ expect_json('due_date_from_milestones', nil)
+ expect_json('start_date', nil)
+ expect_json('due_date', nil)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb b/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb
new file mode 100644
index 00000000000..a13b06863c0
--- /dev/null
+++ b/qa/qa/specs/features/api/ee_geo/geo_nodes_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'Geo Nodes API' do
+ before(:all) do
+ get_personal_access_token
+ end
+
+ shared_examples 'retrieving configuration about Geo nodes' do
+ it 'GET /geo_nodes' do
+ get api_endpoint('/geo_nodes')
+
+ expect_status(200)
+ expect(json_body.size).to be >= 2
+ expect_json('?', primary: true)
+ expect_json_types('*', primary: :boolean, current: :boolean,
+ files_max_capacity: :integer, repos_max_capacity: :integer,
+ clone_protocol: :string, _links: :object)
+ end
+
+ it 'GET /geo_nodes/:id' do
+ get api_endpoint("/geo_nodes/#{geo_node[:id]}")
+
+ expect_status(200)
+ expect(json_body).to eq geo_node
+ end
+ end
+
+ describe 'Geo Nodes API on primary node', :geo do
+ before(:context) do
+ fetch_nodes(:geo_primary)
+ end
+
+ include_examples 'retrieving configuration about Geo nodes' do
+ let(:geo_node) { @primary_node }
+ end
+
+ describe 'editing a Geo node' do
+ it 'PUT /geo_nodes/:id for secondary node' do
+ endpoint = api_endpoint("/geo_nodes/#{@secondary_node[:id]}")
+ new_attributes = { enabled: false, files_max_capacity: 1000, repos_max_capacity: 2000 }
+
+ put endpoint, new_attributes
+
+ expect_status(200)
+ expect_json(new_attributes)
+
+ # restore the original values
+ put endpoint, { enabled: @secondary_node[:enabled],
+ files_max_capacity: @secondary_node[:files_max_capacity],
+ repos_max_capacity: @secondary_node[:repos_max_capacity] }
+
+ expect_status(200)
+ end
+ end
+ end
+
+ describe 'Geo Nodes API on secondary node', :geo do
+ before(:context) do
+ fetch_nodes(:geo_secondary)
+ end
+
+ include_examples 'retrieving configuration about Geo nodes' do
+ let(:geo_node) { @nodes.first }
+ end
+ end
+
+ def api_endpoint(endpoint)
+ QA::Runtime::API::Request.new(@api_client, endpoint).url
+ end
+
+ def fetch_nodes(node_type)
+ @api_client = Runtime::API::Client.new(node_type, personal_access_token: @personal_access_token)
+
+ get api_endpoint('/geo_nodes')
+
+ @nodes = json_body
+ @primary_node = @nodes.detect { |node| node[:primary] == true }
+ @secondary_node = @nodes.detect { |node| node[:primary] == false }
+ end
+
+ # go to the primary and create a personal_access_token, which will be used
+ # for accessing both the primary and secondary
+ def get_personal_access_token
+ api_client = Runtime::API::Client.new(:geo_primary)
+ @personal_access_token = api_client.personal_access_token
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb
new file mode 100644
index 00000000000..0b5b2540042
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_ldap_sync_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage', :orchestrated, :ldap_tls, :ldap_no_tls do
+ describe 'LDAP Group sync' do
+ include Support::Api
+
+ before(:all) do
+ # Create the sandbox group as the LDAP user. Without this the admin user
+ # would own the sandbox group and then in subsequent tests the LDAP user
+ # would not have enough permission to push etc.
+ Resource::Sandbox.fabricate_via_api!
+
+ # Create an admin personal access token and use it for the remaining API calls
+ @original_personal_access_token = Runtime::Env.personal_access_token
+
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?
+ end
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+
+ Runtime::Env.personal_access_token = Resource::PersonalAccessToken.fabricate!.access_token
+ Page::Main::Menu.perform(&:sign_out)
+ end
+
+ after(:all) do
+ # Restore the original personal access token so that subsequent tests
+ # don't perform API calls as an admin user while logged in as a non-root
+ # LDAP user
+ Runtime::Env.personal_access_token = @original_personal_access_token
+ end
+
+ context 'using group cn method' do
+ let(:ldap_users) do
+ [
+ {
+ name: 'ENG User 1',
+ username: 'enguser1',
+ email: 'enguser1@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=enguser1,ou=people,ou=global groups,dc=example,dc=org'
+ },
+ {
+ name: 'ENG User 2',
+ username: 'enguser2',
+ email: 'enguser2@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=enguser2,ou=people,ou=global groups,dc=example,dc=org'
+ },
+ {
+ name: 'ENG User 3',
+ username: 'enguser3',
+ email: 'enguser3@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=enguser3,ou=people,ou=global groups,dc=example,dc=org'
+ }
+ ]
+ end
+ let(:owner_user) { 'enguser1' }
+ let(:sync_users) { ['ENG User 2', 'ENG User 3'] }
+
+ before do
+ create_users_via_api(ldap_users)
+ group = create_group_and_add_user_via_api(owner_user, 'Synched-engineering-group')
+ signin_and_visit_group_as_user(owner_user, group)
+
+ EE::Page::Group::Menu.perform(&:go_to_ldap_sync_settings)
+
+ EE::Page::Group::Settings::LDAPSync.perform do |page|
+ page.set_sync_method('LDAP Group cn')
+ page.set_group_cn('Engineering')
+ page.click_add_sync_button
+ end
+
+ EE::Page::Group::Menu.perform(&:click_group_members_item)
+ end
+
+ it 'has LDAP users synced' do
+ verify_users_synced(sync_users)
+ end
+ end
+
+ context 'user filter method' do
+ let(:ldap_users) do
+ [
+ {
+ name: 'HR User 1',
+ username: 'hruser1',
+ email: 'hruser1@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=hruser1,ou=people,ou=global groups,dc=example,dc=org'
+ },
+ {
+ name: 'HR User 2',
+ username: 'hruser2',
+ email: 'hruser2@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=hruser2,ou=people,ou=global groups,dc=example,dc=org'
+ },
+ {
+ name: 'HR User 3',
+ username: 'hruser3',
+ email: 'hruser3@example.org',
+ provider: 'ldapmain',
+ extern_uid: 'uid=hruser3,ou=people,ou=global groups,dc=example,dc=org'
+ }
+ ]
+ end
+ let(:owner_user) { 'hruser1' }
+ let(:sync_users) { ['HR User 2', 'HR User 3'] }
+
+ before do
+ create_users_via_api(ldap_users)
+ group = create_group_and_add_user_via_api(owner_user, 'Synched-human-resources-group')
+ signin_and_visit_group_as_user(owner_user, group)
+
+ EE::Page::Group::Menu.perform(&:go_to_ldap_sync_settings)
+
+ EE::Page::Group::Settings::LDAPSync.perform do |page|
+ page.set_user_filter('(&(objectClass=person)(cn=HR*))')
+ page.click_add_sync_button
+ end
+
+ EE::Page::Group::Menu.perform(&:click_group_members_item)
+ end
+
+ it 'has LDAP users synced' do
+ verify_users_synced(sync_users)
+ end
+ end
+
+ def create_users_via_api(users)
+ users.each do |user|
+ Resource::User.fabricate_via_api! do |resource|
+ resource.username = user[:username]
+ resource.name = user[:name]
+ resource.email = user[:email]
+ resource.extern_uid = user[:extern_uid]
+ resource.provider = user[:provider]
+ end
+ end
+ end
+
+ def add_user_to_group_via_api(user, group)
+ api_client = Runtime::API::Client.new(:gitlab)
+ response = get Runtime::API::Request.new(api_client, "/users?username=#{user}").url
+ post Runtime::API::Request.new(api_client, group.api_members_path).url, { user_id: parse_body(response).first[:id], access_level: '50' }
+ end
+
+ def create_group_and_add_user_via_api(user_name, group_name)
+ group = Resource::Group.fabricate_via_api! do |resource|
+ resource.path = "#{group_name}-#{SecureRandom.hex(4)}"
+ end
+
+ add_user_to_group_via_api(user_name, group)
+
+ group
+ end
+
+ def signin_and_visit_group_as_user(user_name, group)
+ user = Struct.new(:ldap_username, :ldap_password).new(user_name, 'password')
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform do |login_page|
+ login_page.sign_in_using_ldap_credentials(user)
+ end
+
+ group.visit!
+ end
+
+ def verify_users_synced(expected_users)
+ EE::Page::Group::Members.perform do |page|
+ page.click_sync_now
+ users_synchronised = page.retry_until(reload: true) do
+ expected_users.map { |user| page.has_content?(user) }.all?
+ end
+ expect(users_synchronised).to be_truthy
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb
new file mode 100644
index 00000000000..305cde0f6c9
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/ee_group/group_saml_sso_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage', :orchestrated, :group_saml do
+ describe 'Group SAML SSO' do
+ include Support::Api
+
+ before(:all) do
+ @group = Resource::Sandbox.fabricate!
+ end
+
+ before do
+ unless Page::Main::Menu.perform(&:has_personal_area?)
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ end
+
+ @group.visit!
+ end
+
+ it 'User logs in to group with SAML SSO' do
+ EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
+
+ EE::Page::Group::Settings::SamlSSO.perform do |page|
+ page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url)
+ page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint)
+ page.click_save_changes
+
+ page.click_user_login_url_link
+ end
+
+ EE::Page::Group::SamlSSOSignIn.perform(&:click_signin)
+
+ login_to_idp_if_required_and_expect_success
+
+ EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
+
+ EE::Page::Group::Settings::SamlSSO.perform(&:click_user_login_url_link)
+
+ EE::Page::Group::SamlSSOSignIn.perform(&:click_signin)
+
+ expect(page).to have_content("Already signed in with SAML for #{Runtime::Env.sandbox_name}")
+ end
+
+ it 'Lets group admin test settings' do
+ EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
+
+ EE::Page::Group::Settings::SamlSSO.perform do |page|
+ page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url)
+ page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint)
+ page.click_save_changes
+
+ page.click_test_button
+ end
+
+ login_to_idp_if_required_and_expect_success
+
+ expect(page).to have_content("Test SAML SSO")
+ end
+
+ context 'Enforced SSO' do
+ before do
+ Runtime::Feature.enable("enforced_sso")
+ Runtime::Feature.enable("enforced_sso_requires_session")
+ end
+
+ it 'user clones and pushes to project within a group using Git HTTP' do
+ branch_name = "new_branch"
+
+ user = Resource::User.new.tap do |user|
+ user.name = 'SAML Developer'
+ user.username = 'saml_dev'
+ end
+
+ create_user_via_api(user)
+
+ add_user_to_group_via_api(user.username, @group, '30')
+
+ EE::Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
+
+ EE::Page::Group::Settings::SamlSSO.perform do |page|
+ page.enforce_sso
+ page.set_id_provider_sso_url(QA::EE::Runtime::Saml.idp_sso_url)
+ page.set_cert_fingerprint(QA::EE::Runtime::Saml.idp_certificate_fingerprint)
+
+ page.click_save_changes
+ end
+
+ @project = Resource::Project.fabricate! do |project|
+ project.name = 'project-in-saml-enforced-group'
+ project.description = 'project in SAML enforced gorup for git clone test'
+ project.group = @group
+ project.initialize_with_readme = true
+ end
+
+ @project.visit!
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.branch_name = branch_name
+ push.user = user
+ end
+ end
+ end
+ after(:all) do
+ remove_group(@group)
+ end
+ end
+
+ def login_to_idp_if_required_and_expect_success
+ Vendor::SAMLIdp::Page::Login.perform { |login_page| login_page.login_if_required }
+ expect(page).to have_content("SAML for #{Runtime::Env.sandbox_name} was added to your connected accounts")
+ .or have_content("Already signed in with SAML for #{Runtime::Env.sandbox_name}")
+ end
+
+ def remove_group(group)
+ api_client = Runtime::API::Client.new(:gitlab)
+ delete Runtime::API::Request.new(api_client, "/groups/#{group.path}").url
+ end
+
+ def create_user_via_api(user)
+ Resource::User.fabricate_via_api! do |resource|
+ resource.username = user.username
+ resource.name = user.name
+ resource.email = user.email
+ resource.password = user.password
+ end
+ end
+
+ def add_user_to_group_via_api(username, group, access_level)
+ api_client = Runtime::API::Client.new(:gitlab)
+ response = get Runtime::API::Request.new(api_client, "/users?username=#{username}").url
+ post Runtime::API::Request.new(api_client, group.api_members_path).url, { user_id: parse_body(response).first[:id], access_level: access_level }
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb
new file mode 100644
index 00000000000..bf9fe383c0b
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/ee_ldap/admin_ldap_sync_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage', :orchestrated, :ldap_no_tls, :ldap_tls do
+ describe 'LDAP admin sync' do
+ it 'Syncs admin users' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ Page::Main::Login.perform do |login_page|
+ user = Struct.new(:ldap_username, :ldap_password).new('adminuser1', 'password')
+
+ login_page.sign_in_using_ldap_credentials(user)
+ end
+
+ Page::Main::Menu.perform do |menu|
+ expect(menu).to have_personal_area
+
+ # The ldap_sync_worker_cron job is set to run every minute
+ admin_synchronised = menu.wait(max: 80, interval: 1, reload: true) do
+ menu.has_admin_area_link?
+ end
+
+ expect(admin_synchronised).to be_truthy
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb
new file mode 100644
index 00000000000..5c6b77a6a10
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/ee_project_templates_spec.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+require 'securerandom'
+
+module QA
+ context :manage do
+ describe 'Project templates' do
+ before(:all) do
+ @files = [
+ {
+ name: 'file.txt',
+ content: 'foo'
+ },
+ {
+ name: 'README.md',
+ content: 'bar'
+ }
+ ]
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @template_container_group_name = "instance-template-container-group-#{SecureRandom.hex(8)}"
+
+ template_container_group = QA::Resource::Group.fabricate! do |group|
+ group.path = @template_container_group_name
+ group.description = 'Instance template container group'
+ end
+
+ @template_project = Resource::Project.fabricate! do |project|
+ project.name = 'template-project-1'
+ project.group = template_container_group
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @template_project
+ push.files = @files
+ push.commit_message = 'Add test files'
+ end
+ end
+
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/51
+ context 'instance level', :quarantine do
+ before do
+ # Log out if already logged in
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+
+ Page::Main::Menu.perform(&:click_admin_area)
+ Page::Admin::Menu.perform(&:go_to_template_settings)
+
+ EE::Page::Admin::Settings::Templates.perform do |page|
+ page.choose_custom_project_template("#{@template_container_group_name}")
+ end
+
+ group = Resource::Group.fabricate_via_api!
+ group.visit!
+
+ Page::Group::Show.perform(&:go_to_new_project)
+
+ Page::Project::New.perform(&:go_to_create_from_template_instance_tab)
+ end
+
+ it 'successfully imports the project using template' do
+ Page::Project::New.perform do |page|
+ expect(page.instance_template_tab_badge_text).to eq "1"
+ expect(page).to have_text(@template_project.name)
+ end
+
+ create_project_using_template(project_name: 'Project using instance level project template',
+ namespace: Runtime::Namespace.path,
+ template_name: @template_project.name)
+
+ Page::Project::Show.perform(&:wait_for_import_success)
+ @files.each do |file|
+ expect(page).to have_content(file[:name])
+ end
+ end
+ end
+
+ context 'group level' do
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ Page::Main::Menu.perform(&:go_to_groups)
+ Page::Dashboard::Groups.perform { |page| page.click_group(Runtime::Namespace.sandbox_name) }
+ Page::Project::Menu.perform(&:click_settings)
+
+ EE::Page::Group::Settings::General.perform do |settings|
+ settings.choose_custom_project_template("#{@template_container_group_name}")
+ end
+
+ group = Resource::Group.fabricate_via_api!
+ group.visit!
+
+ Page::Group::Show.perform(&:go_to_new_project)
+
+ Page::Project::New.perform(&:go_to_create_from_template_group_tab)
+ end
+
+ it 'successfully imports the project using template' do
+ Page::Project::New.perform do |page|
+ expect(page.group_template_tab_badge_text).to eq "1"
+ expect(page).to have_text(@template_container_group_name)
+ expect(page).to have_text(@template_project.name)
+ end
+
+ create_project_using_template(project_name: 'Project using group level project template',
+ namespace: Runtime::Namespace.sandbox_name,
+ template_name: @template_project.name)
+
+ Page::Project::Show.perform(&:wait_for_import_success)
+ @files.each do |file|
+ expect(page).to have_content(file[:name])
+ end
+ end
+ end
+
+ def create_project_using_template(project_name:, namespace:, template_name:)
+ Page::Project::New.perform do |page|
+ page.use_template_for_project(template_name)
+ page.choose_namespace(namespace)
+ page.choose_name("#{project_name} #{SecureRandom.hex(8)}")
+ page.add_description("#{project_name}")
+ page.set_visibility('Public')
+ page.create_new_project
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb
new file mode 100644
index 00000000000..8c3e13a76b6
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/create_edit_delete_epic_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module QA
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/19
+ context 'Plan', :quarantine do
+ describe 'Epics Creation' do
+ before(:all) do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+ end
+
+ it 'user creates, edits, deletes epic' do
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = 'Issue for epics tests'
+ issue.labels = []
+ end
+
+ epic = EE::Resource::Epic.fabricate_via_browser_ui! do |epic|
+ epic.group = issue.project.group
+ epic.title = 'My First Epic'
+ end
+
+ expect(page).to have_content('My First Epic')
+
+ # Edit Epics
+ EE::Page::Group::Epic::Show.act { click_edit_button }
+
+ EE::Page::Group::Epic::Edit.perform do |edit_page|
+ edit_page.set_description('My Edited Epic Description')
+ edit_page.set_title('My Edited Epic')
+ edit_page.save_changes
+ expect(edit_page).to have_content('My Edited Epic')
+ end
+
+ # Add/Remove Issues to/from Epics
+ EE::Page::Group::Epic::Show.perform do |show_page|
+ show_page.add_issue_to_epic(issue.web_url)
+ expect(show_page).to have_content('added issue')
+ expect(show_page).to have_content('My Edited Epic')
+
+ show_page.remove_issue_from_epic
+ expect(show_page).to have_content('removed issue')
+ end
+
+ # Comment on Epics
+ EE::Page::Group::Epic::Show.act { add_comment_to_epic('My Epic Comments') }
+
+ expect(page).to have_content('My Epic Comments')
+
+ # Add Issue to Epic using quick actions
+ issue.visit!
+
+ Page::Project::Issue::Show.perform do |show_page|
+ show_page.wait_for_related_issues_to_load
+ show_page.comment("/epic #{epic.web_url}")
+ show_page.comment("/remove_epic")
+ expect(show_page).to have_content('removed from epic')
+ end
+
+ epic.visit!
+
+ expect(page).to have_content('added issue', count: 2)
+ expect(page).to have_content('removed issue', count: 2)
+
+ # Close Epic
+ EE::Page::Group::Epic::Show.act { close_reopen_epic }
+
+ expect(page).to have_content('Closed')
+
+ # Reopen Epic
+ EE::Page::Group::Epic::Show.act { close_reopen_epic }
+
+ expect(page).to have_content('Open')
+
+ # Delete Epics
+ EE::Page::Group::Epic::Show.act { click_edit_button }
+
+ EE::Page::Group::Epic::Edit.perform do |edit_page|
+ edit_page.delete_epic
+ expect(edit_page).to have_content('The epic was successfully deleted')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb
new file mode 100644
index 00000000000..731da801700
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/ee_epic/promote_issue_to_epic_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'promote issue to epic' do
+ let(:issue_title) { "My Awesome Issue #{SecureRandom.hex(8)}" }
+
+ it 'user promotes issue to an epic' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ group = Resource::Group.fabricate_via_api!
+
+ project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'promote-issue-to-epic'
+ project.description = 'Project to promote issue to epic'
+ project.group = group
+ end
+
+ Resource::Issue.fabricate_via_browser_ui! do |issue|
+ issue.title = issue_title
+ issue.project = project
+ end
+
+ Page::Project::Issue::Show.perform do |show|
+ # Due to the randomness of tests execution, sometimes a previous test
+ # may have changed the filter, which makes the below action needed.
+ # TODO: Make this test completely independent, not requiring the below step.
+ show.select_all_activities_filter
+ # We add a space together with the '/promote' string to avoid test flakiness
+ # due to the tooltip '/promote Promote issue to an epic (may expose
+ # confidential information)' from being shown, which may cause the click not
+ # to work properly.
+ show.comment('/promote ')
+ end
+
+ group.visit!
+ QA::EE::Page::Group::Menu.perform(&:click_group_epics_link)
+ QA::EE::Page::Group::Epic::Index.perform do |index|
+ index.click_first_epic(QA::EE::Page::Group::Epic::Show)
+ end
+
+ expect(page).to have_content(issue_title)
+ expect(page).to have_content(/promoted from issue .* \(closed\)/)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb
new file mode 100644
index 00000000000..39ae71dd676
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/ee_scoped_labels/editing_scoped_labels_spec.rb
@@ -0,0 +1,40 @@
+module QA
+ context 'Plan' do
+ describe 'Editing scoped labels on issues' do
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @initial_label = 'animal::fox'
+ @new_label_same_scope = 'animal::dolphin'
+ @new_label_different_scope = 'plant::orchid'
+
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = 'Issue to test the scoped labels'
+ issue.labels = @initial_label
+ end
+
+ [@new_label_same_scope, @new_label_different_scope].each do |label|
+ Resource::Label.fabricate_via_api! do |l|
+ l.project = issue.project.id
+ l.title = label
+ end
+ end
+
+ issue.visit!
+ end
+
+ it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
+ Page::Project::Issue::Show.perform do |issue_page|
+ issue_page.select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
+
+ expect(page).to have_content("added #{@initial_label}")
+ expect(page).to have_content("added #{@new_label_same_scope} #{@new_label_different_scope} labels and removed #{@initial_label}")
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_same_scope)
+ expect(issue_page.text_of_labels_block).to have_content(@new_label_different_scope)
+ expect(issue_page.text_of_labels_block).not_to have_content(@initial_label)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb
new file mode 100644
index 00000000000..490f3d84d4a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/ee_add_batch_comments_in_merge_request_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'batch comments in merge request' do
+ it 'user submits, discards batch comments' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'project-with-merge-request'
+ end
+
+ Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = 'This is a merge request'
+ merge_request.description = 'Great feature'
+ merge_request.project = project
+ end
+
+ Page::MergeRequest::Show.perform do |show_page|
+ show_page.click_discussions_tab
+
+ show_page.start_discussion("I'm starting a new discussion")
+ expect(show_page).to have_content("I'm starting a new discussion")
+
+ show_page.type_reply_to_discussion("Could you please check this?")
+ show_page.comment_now
+ expect(show_page).to have_content("Could you please check this?")
+ expect(show_page).to have_content("0/1 discussion resolved")
+
+ show_page.type_reply_to_discussion("Could you also check that?")
+ show_page.resolve_review_discussion
+ show_page.start_review
+ expect(show_page).to have_content("Could you also check that?")
+ expect(show_page).to have_content("Finish review 1")
+
+ show_page.click_diffs_tab
+
+ show_page.add_comment_to_diff("Can you check this line of code?")
+ show_page.comment_now
+ expect(show_page).to have_content("Can you check this line of code?")
+
+ show_page.type_reply_to_discussion("And this syntax as well?")
+ show_page.resolve_review_discussion
+ show_page.start_review
+ expect(show_page).to have_content("And this syntax as well?")
+ expect(show_page).to have_content("Finish review 2")
+
+ show_page.submit_pending_reviews
+ expect(show_page).to have_content("2/2 discussions resolved")
+
+ show_page.type_reply_to_discussion("Unresolving this discussion")
+ show_page.unresolve_review_discussion
+ show_page.comment_now
+ expect(show_page).to have_content("1/2 discussions resolved")
+ end
+
+ Page::MergeRequest::Show.perform do |show_page|
+ show_page.click_discussions_tab
+
+ show_page.type_reply_to_discussion("Planning to discard this comment")
+ show_page.start_review
+
+ expect(show_page).to have_content("Finish review 1")
+ show_page.discard_pending_reviews
+
+ expect(show_page).not_to have_content("Planning to discard this comment")
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb
new file mode 100644
index 00000000000..e10a8f7eec4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/ee_assign_code_owners_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module QA
+ # https://gitlab.com/gitlab-org/quality/staging/issues/39
+ context 'Create', :quarantine do
+ describe 'Codeowners' do
+ it 'merge request assigns code owners as approvers' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create one user to be the assigned approver and another user who will
+ # not be an approver
+ approver = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
+ non_approver = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2)
+
+ # Create a project and assign the users to it
+ project = Resource::Project.fabricate! do |project|
+ project.name = "assign-approvers"
+ end
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_members_settings)
+ Page::Project::Settings::Members.perform do |members_page|
+ members_page.add_member(approver.username)
+ members_page.add_member(non_approver.username)
+ end
+
+ # Push CODEOWNERS to master
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = 'CODEOWNERS'
+ push.file_content = <<~CONTENT
+ CODEOWNERS @#{approver.username}
+ CONTENT
+ push.commit_message = 'Add CODEOWNERS and test files'
+ end
+
+ # Push a new CODEOWNERS file and create a merge request
+ Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = 'This is a merge request'
+ merge_request.description = 'Change code owners'
+ merge_request.project = project_push.project
+ merge_request.file_name = 'CODEOWNERS'
+ merge_request.file_content = <<~CONTENT
+ CODEOWNERS @#{non_approver.username}
+ CONTENT
+ end
+
+ # Check that the merge request assigns the original code owner as an
+ # approver (because the current CODEOWNERS file in the master branch
+ # doesn't have the new owner yet)
+ Page::MergeRequest::Show.perform do |mr_page|
+ mr_page.edit!
+ expect(mr_page.approvers).to include(approver.name)
+ expect(mr_page.approvers).not_to include(non_approver.name)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb
new file mode 100644
index 00000000000..a3d7f54e085
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/ee_code_owners_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Codeowners' do
+ let(:files) do
+ [
+ {
+ name: 'file.txt',
+ content: 'foo'
+ },
+ {
+ name: 'README.md',
+ content: 'bar'
+ }
+ ]
+ end
+
+ before do
+ # Add two new users to a project as members
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
+ @user2 = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2)
+
+ @project = Resource::Project.fabricate! do |project|
+ project.name = "codeowners"
+ end
+ @project.visit!
+
+ Page::Project::Menu.perform(&:go_to_members_settings)
+ Page::Project::Settings::Members.perform do |members_page|
+ members_page.add_member(@user.username)
+ members_page.add_member(@user2.username)
+ end
+ end
+
+ it 'displays owners specified in CODEOWNERS file' do
+ codeowners_file_content =
+ <<-CONTENT
+ * @#{@user2.username}
+ *.txt @#{@user.username}
+ CONTENT
+ files << {
+ name: 'CODEOWNERS',
+ content: codeowners_file_content
+ }
+
+ # Push CODEOWNERS and test files to the project
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.files = files
+ push.commit_message = 'Add CODEOWNERS and test files'
+ end
+ @project.visit!
+
+ # Check the files and code owners
+ Page::Project::Show.perform do |project_page|
+ project_page.click_file 'file.txt'
+ end
+
+ expect(page).to have_content(@user.name)
+ expect(page).not_to have_content(@user2.name)
+
+ @project.visit!
+ Page::Project::Show.perform do |project_page|
+ project_page.click_file 'README.md'
+ end
+
+ expect(page).to have_content(@user2.name)
+ expect(page).not_to have_content(@user.name)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb
new file mode 100644
index 00000000000..6d1c8078ce7
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/attachment_replication_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab Geo attachment replication' do
+ let(:file_to_attach) { File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) }
+
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ it 'user uploads attachment to the primary node' do
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ @project = Resource::Project.fabricate! do |project|
+ project.name = 'project-for-issues'
+ project.description = 'project for adding issues'
+ end
+
+ @issue = Resource::Issue.fabricate! do |issue|
+ issue.title = 'My geo issue'
+ issue.project = @project
+ end
+
+ Page::Project::Issue::Show.perform do |show|
+ show.comment('See attached banana for scale', attachment: file_to_attach)
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do |session|
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ expect(page).to have_content 'You are on a secondary, read-only Geo node'
+
+ Page::Main::Menu.perform do |menu|
+ menu.go_to_projects
+ end
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(@project.name)
+
+ dashboard.go_to_project(@project.name)
+ end
+
+ Page::Project::Menu.act { click_issues }
+
+ Page::Project::Issue::Index.perform do |index|
+ index.wait_for_issue_replication(@issue)
+ end
+
+ image_url = find('a[href$="banana_sample.gif"]')[:href]
+
+ Page::Project::Issue::Show.perform do |show|
+ # Wait for attachment replication
+ found = show.wait(reload: false) do
+ show.asset_exists?(image_url)
+ end
+
+ expect(found).to be_truthy
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb
new file mode 100644
index 00000000000..36a932f7f2d
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/http_push_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab HTTP push' do
+ let(:file_name) { 'README.md' }
+
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ context 'regular git commit' do
+ it 'is replicated to the secondary' do
+ file_content = 'This is a Geo project! Commit from primary.'
+ project = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over HTTP directly to the primary
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content}"
+ push.commit_message = 'Add README.md'
+ end
+
+ # Validate git push worked and file exists with content
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ Page::Main::Menu.perform { |menu| menu.go_to_projects }
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Validate the content has been sync'd from the primary
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_name)
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+ end
+ end
+
+ context 'git-lfs commit' do
+ it 'is replicated to the secondary' do
+ file_content = 'This is a Geo project!'
+ lfs_file_content = 'The rendered file could not be displayed because it is stored in LFS.'
+ project = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over HTTP directly to the primary
+ push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.use_lfs = true
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content}"
+ push.commit_message = 'Add README.md'
+ end
+
+ expect(push.output).to match(/Locking support detected on remote/)
+
+ # Validate git push worked and file exists with content
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(lfs_file_content)
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ Page::Main::Menu.perform { |menu| menu.go_to_projects }
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Validate the content has been sync'd from the primary
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_name)
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(lfs_file_content)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb
new file mode 100644
index 00000000000..f7f2c37a105
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/http_push_to_secondary_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab Geo HTTP push secondary' do
+ let(:file_content_primary) { 'This is a Geo project! Commit from primary.' }
+ let(:file_content_secondary) { 'This is a Geo project! Commit from secondary.' }
+
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ context 'regular git commit' do
+ it 'is redirected to the primary and ultimately replicated to the secondary' do
+ file_name = 'README.md'
+ project = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over HTTP directly to the primary
+ #
+ # This push is required to ensure we have the primary credentials
+ # written out to the .netrc
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content_primary}"
+ push.commit_message = "Add #{file_name}"
+ end
+ project.visit!
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ Page::Main::Menu.perform(&:go_to_projects)
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Grab the HTTP URI for the secondary and store as 'location'
+ location = Page::Project::Show.perform do |project_page|
+ project_page.wait_for_repository_replication
+ project_page.repository_clone_http_location
+ end
+
+ # Perform a git push over HTTP at the secondary
+ push = Resource::Repository::Push.fabricate! do |push|
+ push.new_branch = false
+ push.repository_http_uri = location.uri
+ push.file_name = file_name
+ push.file_content = "# #{file_content_secondary}"
+ push.commit_message = "Update #{file_name}"
+ end
+
+ # We need to strip off the user from the URI, otherwise we won't
+ # get the correct output produced from the git CLI.
+ primary_uri = project.repository_http_location.uri
+ primary_uri.user = nil
+
+ # The git cli produces the 'warning: redirecting to..' output
+ # internally.
+ expect(push.output).to match(/warning: redirecting to #{primary_uri.to_s}/)
+
+ # Validate git push worked and new content is visible
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_content_secondary)
+ show.refresh
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content_secondary)
+ end
+ end
+ end
+ end
+
+ context 'git-lfs commit' do
+ it 'is redirected to the primary and ultimately replicated to the secondary' do
+ file_name_primary = 'README.md'
+ file_name_secondary = 'README_MORE.md'
+ project = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over HTTP directly to the primary
+ #
+ # This push is required to ensure we have the primary credentials
+ # written out to the .netrc
+ Resource::Repository::Push.fabricate! do |push|
+ push.use_lfs = true
+ push.repository_http_uri = project.repository_http_location.uri
+ push.file_name = file_name_primary
+ push.file_content = "# #{file_content_primary}"
+ push.commit_message = "Add #{file_name_primary}"
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ Page::Main::Menu.perform(&:go_to_projects)
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Grab the HTTP URI for the secondary and store as 'location'
+ location = Page::Project::Show.perform do |project_page|
+ project_page.wait_for_repository_replication
+ project_page.repository_clone_http_location
+ end
+
+ # Perform a git push over HTTP at the secondary
+ push = Resource::Repository::Push.fabricate! do |push|
+ push.use_lfs = true
+ push.new_branch = false
+ push.repository_http_uri = location.uri
+ push.file_name = file_name_secondary
+ push.file_content = "# #{file_content_secondary}"
+ push.commit_message = "Add #{file_name_secondary}"
+ end
+
+ # We need to strip off the user from the URI, otherwise we won't
+ # get the correct output produced from the git CLI.
+ primary_uri = project.repository_http_location.uri
+ primary_uri.user = nil
+
+ # The git cli produces the 'warning: redirecting to..' output
+ # internally.
+ expect(push.output).to match(/warning: redirecting to #{primary_uri.to_s}/)
+ expect(push.output).to match(/Locking support detected on remote "#{location.uri.to_s}"/)
+
+ # Validate git push worked and new content is visible
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_name_secondary)
+ show.refresh
+
+ expect(page).to have_content(file_name_secondary)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb
new file mode 100644
index 00000000000..7eb7ceb371b
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/rename_replication_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab Geo project rename replication' do
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ it 'user renames project' do
+ # create the project and push code
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-before-rename'
+ project.description = 'Geo project to be renamed'
+ end
+
+ geo_project_name = project.name
+ expect(project.name).to include 'geo-before-rename'
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = 'README.md'
+ push.file_content = '# This is Geo project!'
+ push.commit_message = 'Add README.md'
+ end
+
+ # rename the project
+ Page::Main::Menu.act { go_to_projects }
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.go_to_project(geo_project_name)
+ end
+
+ Page::Project::Menu.act { click_settings }
+
+ @geo_project_renamed = "geo-after-rename-#{SecureRandom.hex(8)}"
+ Page::Project::Settings::Main.perform do |settings|
+ settings.rename_project_to(@geo_project_renamed)
+ expect(page).to have_content "Project '#{@geo_project_renamed}' was successfully updated."
+
+ settings.expand_advanced_settings do |page|
+ page.update_project_path_to(@geo_project_renamed)
+ end
+ end
+ end
+
+ # check renamed project exist on secondary node
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ Page::Main::Menu.perform do |menu|
+ menu.go_to_projects
+ end
+
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(@geo_project_renamed)
+
+ dashboard.go_to_project(@geo_project_renamed)
+ end
+
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication
+
+ expect(page).to have_content 'README.md'
+ expect(page).to have_content 'This is Geo project!'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb
new file mode 100644
index 00000000000..ab664bf659f
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_spec.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab SSH push' do
+ let(:file_name) { 'README.md' }
+
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ context 'regular git commit' do
+ it "is replicated to the secondary" do
+ key_title = "key for ssh tests #{Time.now.to_f}"
+ file_content = 'This is a Geo project! Commit from primary.'
+ project = nil
+ key = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new SSH key for the user
+ key = Resource::SSHKey.fabricate! do |resource|
+ resource.title = key_title
+ end
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over SSH directly to the primary
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.ssh_key = key
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content}"
+ push.commit_message = 'Add README.md'
+ end
+
+ # Validate git push worked and file exists with content
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ # Ensure the SSH key has replicated
+ Page::Main::Menu.act { click_settings_link }
+ Page::Profile::Menu.act { click_ssh_keys }
+
+ expect(page).to have_content(key_title)
+ expect(page).to have_content(key.fingerprint)
+
+ # Ensure project has replicated
+ Page::Main::Menu.perform { |menu| menu.go_to_projects }
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Validate the content has been sync'd from the primary
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_content)
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+ end
+ end
+
+ context 'git-lfs commit' do
+ it "is replicated to the secondary" do
+ key_title = "key for ssh tests #{Time.now.to_f}"
+ file_content = 'The rendered file could not be displayed because it is stored in LFS.'
+ project = nil
+ key = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new SSH key for the user
+ key = Resource::SSHKey.fabricate! do |resource|
+ resource.title = key_title
+ end
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over SSH directly to the primary
+ push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.use_lfs = true
+ push.ssh_key = key
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content}"
+ push.commit_message = 'Add README.md'
+ end
+
+ expect(push.output).to match(/Locking support detected on remote/)
+
+ # Validate git push worked and file exists with content
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ # Ensure the SSH key has replicated
+ Page::Main::Menu.act { click_settings_link }
+ Page::Profile::Menu.act { click_ssh_keys }
+
+ expect(page).to have_content(key_title)
+ expect(page).to have_content(key.fingerprint)
+
+ # Ensure project has replicated
+ Page::Main::Menu.perform { |menu| menu.go_to_projects }
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Validate the content has been sync'd from the primary
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_name)
+
+ expect(page).to have_content(file_name)
+ expect(page).to have_content(file_content)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb
new file mode 100644
index 00000000000..188615420fd
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_geo/ssh_push_to_secondary_spec.rb
@@ -0,0 +1,206 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Geo', :orchestrated, :geo do
+ describe 'GitLab SSH push to secondary' do
+ let(:file_content_primary) { 'This is a Geo project! Commit from primary.' }
+ let(:file_content_secondary) { 'This is a Geo project! Commit from secondary.' }
+
+ after do
+ # Log out so subsequent tests can start unauthenticated
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Dashboard::Projects)
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+ end
+
+ context 'regular git commit' do
+ it 'is proxied to the primary and ultimately replicated to the secondary' do
+ file_name = 'README.md'
+ key_title = "key for ssh tests #{Time.now.to_f}"
+ file_content = 'This is a Geo project! Commit from secondary.'
+ project = nil
+ key = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new SSH key for the user
+ key = Resource::SSHKey.fabricate! do |resource|
+ resource.title = key_title
+ end
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over SSH directly to the primary
+ #
+ # This push is required to ensure we have the primary credentials
+ # written out to the .netrc
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.ssh_key = key
+ push.project = project
+ push.file_name = file_name
+ push.file_content = "# #{file_content_primary}"
+ push.commit_message = "Add #{file_name}"
+ end
+ project.visit!
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ # Ensure the SSH key has replicated
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform do |menu|
+ menu.click_ssh_keys
+ menu.wait_for_key_to_replicate(key_title)
+ end
+
+ expect(page).to have_content(key_title)
+ expect(page).to have_content(key.fingerprint)
+
+ # Ensure project has replicated
+ Page::Main::Menu.perform(&:go_to_projects)
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Grab the SSH URI for the secondary and store as 'location'
+ location = Page::Project::Show.perform do |project_page|
+ project_page.wait_for_repository_replication
+ project_page.repository_clone_ssh_location
+ end
+
+ # Perform a git push over SSH at the secondary
+ push = Resource::Repository::Push.fabricate! do |push|
+ push.new_branch = false
+ push.ssh_key = key
+ push.repository_ssh_uri = location.uri
+ push.file_name = file_name
+ push.file_content = "# #{file_content_secondary}"
+ push.commit_message = "Update #{file_name}"
+ end
+
+ # Remove ssh:// from the URI to ensure we can match accurately
+ # as ssh:// can appear depending on how GitLab is configured.
+ ssh_uri = project.repository_ssh_location.git_uri.to_s.gsub(%r{ssh://}, '')
+
+ expect(push.output).to match(%r{GitLab: We'll help you by proxying this request to the primary: (?:ssh://)?#{ssh_uri}})
+
+ # Validate git push worked and new content is visible
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_content)
+
+ expect(page).to have_content(file_content)
+ end
+ end
+ end
+ end
+
+ context 'git-lfs commit' do
+ it 'is proxied to the primary and ultimately replicated to the secondary' do
+ key_title = "key for ssh tests #{Time.now.to_f}"
+ file_name_primary = 'README.md'
+ file_name_secondary = 'README_MORE.md'
+ project = nil
+ key = nil
+
+ Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
+ # Visit the primary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ # Create a new SSH key for the user
+ key = Resource::SSHKey.fabricate! do |resource|
+ resource.title = key_title
+ end
+
+ # Create a new Project
+ project = Resource::Project.fabricate! do |project|
+ project.name = 'geo-project'
+ project.description = 'Geo test project'
+ end
+
+ # Perform a git push over SSH directly to the primary
+ #
+ # This push is required to ensure we have the primary credentials
+ # written out to the .netrc
+ Resource::Repository::Push.fabricate! do |push|
+ push.use_lfs = true
+ push.ssh_key = key
+ push.repository_ssh_uri = project.repository_ssh_location.uri
+ push.file_name = file_name_primary
+ push.file_content = "# #{file_content_primary}"
+ push.commit_message = "Add #{file_name_primary}"
+ end
+ end
+
+ Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
+ # Visit the secondary node and login
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ EE::Page::Main::Banner.perform do |banner|
+ expect(banner).to have_secondary_read_only_banner
+ end
+
+ # Ensure the SSH key has replicated
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform do |menu|
+ menu.click_ssh_keys
+ menu.wait_for_key_to_replicate(key_title)
+ end
+
+ expect(page).to have_content(key_title)
+ expect(page).to have_content(key.fingerprint)
+
+ # Ensure project has replicated
+ Page::Main::Menu.perform(&:go_to_projects)
+ Page::Dashboard::Projects.perform do |dashboard|
+ dashboard.wait_for_project_replication(project.name)
+ dashboard.go_to_project(project.name)
+ end
+
+ # Grab the SSH URI for the secondary and store as 'location'
+ location = Page::Project::Show.perform do |project_page|
+ project_page.wait_for_repository_replication
+ project_page.repository_clone_ssh_location
+ end
+
+ # Perform a git push over SSH at the secondary
+ push = Resource::Repository::Push.fabricate! do |push|
+ push.use_lfs = true
+ push.new_branch = false
+ push.ssh_key = key
+ push.repository_ssh_uri = location.uri
+ push.file_name = file_name_secondary
+ push.file_content = "# #{file_content_secondary}"
+ push.commit_message = "Add #{file_name_secondary}"
+ end
+
+ ssh_uri = project.repository_ssh_location.git_uri.to_s.gsub(%r{ssh://}, '')
+ expect(push.output).to match(%r{GitLab: We'll help you by proxying this request to the primary: (?:ssh://)?#{ssh_uri}})
+ expect(push.output).to match(/Locking support detected on remote "#{location.uri.to_s}"/)
+
+ # Validate git push worked and new content is visible
+ Page::Project::Show.perform do |show|
+ show.wait_for_repository_replication_with(file_name_secondary)
+ show.refresh
+
+ expect(page).to have_content(file_name_secondary)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb b/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb
new file mode 100644
index 00000000000..f52651f9376
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/ee_secure/create_project_with_secure_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'pathname'
+
+module QA
+ context 'Secure', :docker do
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ end
+
+ describe 'Security Dashboard support' do
+ let(:executor) { "qa-runner-#{Time.now.to_i}" }
+
+ after do
+ Service::Runner.new(executor).remove!
+ end
+
+ it 'displays the Dependency Scanning report in the pipeline' do
+ login
+
+ @project = Resource::Project.fabricate! do |p|
+ p.name = Runtime::Env.auto_devops_project_name || 'project-with-secure'
+ p.description = 'Project with Secure'
+ end
+
+ Resource::Runner.fabricate! do |runner|
+ runner.project = @project
+ runner.name = executor
+ runner.tags = %w[qa test]
+ end
+
+ # Create Secure compatible repo
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = @project
+ push.directory = Pathname
+ .new(__dir__)
+ .join('../../../../ee/fixtures/secure_premade_reports')
+ push.commit_message = 'Create Secure compatible application to serve premade reports'
+ end
+
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('dependency-scanning')
+ 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_on_security
+ expect(pipeline).to have_dependency_report
+ expect(pipeline).to have_content("Dependency scanning detected 1")
+ pipeline.expand_dependency_report
+ expect(pipeline).to have_content("jQuery before 3.4.0")
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb
index 9c1f9904a7a..1b8c926532a 100644
--- a/qa/qa/vendor/saml_idp/page/login.rb
+++ b/qa/qa/vendor/saml_idp/page/login.rb
@@ -12,6 +12,14 @@ module QA
fill_in 'password', with: 'user1pass'
click_on 'Login'
end
+
+ def login_if_required
+ login if login_required?
+ end
+
+ def login_required?
+ page.has_text?('Enter your username and password')
+ end
end
end
end
diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb
index bf3ebce0cfe..2f9e4958ae1 100644
--- a/qa/spec/resource/repository/push_spec.rb
+++ b/qa/spec/resource/repository/push_spec.rb
@@ -19,7 +19,11 @@ describe QA::Resource::Repository::Push do
expect { subject.files = [] }.to raise_error(ArgumentError)
end
- it 'does not raise if files is an array' do
+ it 'raises an error if files is not an array of hashes with :name and :content keys' do
+ expect { subject.files = [{ foo: 'foo' }] }.to raise_error(ArgumentError)
+ end
+
+ it 'does not raise if files is an array of hashes with :name and :content keys' do
expect { subject.files = files }.not_to raise_error
end
end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index caf96a213e1..340831aa06d 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -192,6 +192,30 @@ describe QA::Runtime::Env do
end
end
+ describe '.knapsack?' do
+ it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do
+ stub_env('KNAPSACK_GENERATE_REPORT', 'true')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_REPORT_PATH is defined' do
+ stub_env('KNAPSACK_REPORT_PATH', '/a/path')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do
+ stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do
+ expect(described_class.knapsack?).to be_falsey
+ end
+ end
+
describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)