summaryrefslogtreecommitdiff
path: root/qa/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa')
-rw-r--r--qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml5
-rw-r--r--qa/qa/git/repository.rb62
-rw-r--r--qa/qa/page/base.rb9
-rw-r--r--qa/qa/page/component/breadcrumbs.rb4
-rw-r--r--qa/qa/page/component/ci_badge_link.rb4
-rw-r--r--qa/qa/page/component/clone_panel.rb4
-rw-r--r--qa/qa/page/component/confirm_modal.rb4
-rw-r--r--qa/qa/page/component/custom_metric.rb49
-rw-r--r--qa/qa/page/component/design_management.rb82
-rw-r--r--qa/qa/page/component/groups_filter.rb4
-rw-r--r--qa/qa/page/component/issuable/common.rb4
-rw-r--r--qa/qa/page/component/lazy_loader.rb4
-rw-r--r--qa/qa/page/component/legacy_clone_panel.rb4
-rw-r--r--qa/qa/page/component/note.rb4
-rw-r--r--qa/qa/page/component/project/templates.rb15
-rw-r--r--qa/qa/page/component/select2.rb8
-rw-r--r--qa/qa/page/component/web_ide/alert.rb8
-rw-r--r--qa/qa/page/dashboard/snippet/edit.rb36
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb26
-rw-r--r--qa/qa/page/dashboard/snippet/show.rb65
-rw-r--r--qa/qa/page/file/shared/commit_button.rb4
-rw-r--r--qa/qa/page/file/shared/commit_message.rb4
-rw-r--r--qa/qa/page/file/shared/editor.rb4
-rw-r--r--qa/qa/page/group/sub_menus/common.rb3
-rw-r--r--qa/qa/page/main/terms.rb24
-rw-r--r--qa/qa/page/page_concern.rb16
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb14
-rw-r--r--qa/qa/page/profile/ssh_keys.rb18
-rw-r--r--qa/qa/page/project/issue/index.rb21
-rw-r--r--qa/qa/page/project/issue/show.rb18
-rw-r--r--qa/qa/page/project/job/show.rb72
-rw-r--r--qa/qa/page/project/new.rb6
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb4
-rw-r--r--qa/qa/page/project/operations/metrics/show.rb24
-rw-r--r--qa/qa/page/project/pipeline/index.rb76
-rw-r--r--qa/qa/page/project/pipeline/show.rb112
-rw-r--r--qa/qa/page/project/settings/advanced.rb4
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb9
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb2
-rw-r--r--qa/qa/page/project/settings/common.rb13
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb2
-rw-r--r--qa/qa/page/project/settings/general_pipelines.rb23
-rw-r--r--qa/qa/page/project/settings/incidents.rb37
-rw-r--r--qa/qa/page/project/settings/integrations.rb19
-rw-r--r--qa/qa/page/project/settings/main.rb2
-rw-r--r--qa/qa/page/project/settings/merge_request.rb2
-rw-r--r--qa/qa/page/project/settings/operations.rb23
-rw-r--r--qa/qa/page/project/settings/repository.rb4
-rw-r--r--qa/qa/page/project/settings/services/prometheus.rb36
-rw-r--r--qa/qa/page/project/sub_menus/ci_cd.rb6
-rw-r--r--qa/qa/page/project/sub_menus/common.rb1
-rw-r--r--qa/qa/page/project/sub_menus/issues.rb6
-rw-r--r--qa/qa/page/project/sub_menus/operations.rb12
-rw-r--r--qa/qa/page/project/sub_menus/project.rb6
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb8
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb15
-rw-r--r--qa/qa/page/search/results.rb76
-rw-r--r--qa/qa/resource/api_fabricator.rb33
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb68
-rw-r--r--qa/qa/resource/kubernetes_cluster/base.rb40
-rw-r--r--qa/qa/resource/kubernetes_cluster/project_cluster.rb72
-rw-r--r--qa/qa/resource/pipeline.rb53
-rw-r--r--qa/qa/resource/project.rb34
-rw-r--r--qa/qa/resource/runner.rb27
-rw-r--r--qa/qa/resource/ssh_key.rb10
-rw-r--r--qa/qa/resource/visibility.rb17
-rw-r--r--qa/qa/runtime/api/client.rb2
-rw-r--r--qa/qa/runtime/project.rb35
-rw-r--r--qa/qa/scenario/test/integration/gitaly_ha.rb13
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb59
-rw-r--r--qa/qa/service/praefect_manager.rb27
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/users_spec.rb2
-rw-r--r--qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb1
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb28
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/add_design_add_annotation.rb31
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb63
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb4
-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_personal_snippet_spec.rb102
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb61
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb155
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb97
-rw-r--r--qa/qa/specs/features/sanity/framework_spec.rb2
-rw-r--r--qa/qa/specs/helpers/quarantine.rb130
-rw-r--r--qa/qa/specs/runner.rb2
-rw-r--r--qa/qa/support/api.rb3
-rw-r--r--qa/qa/tools/delete_test_ssh_keys.rb80
-rw-r--r--qa/qa/vendor/jenkins/page/configure_job.rb15
-rw-r--r--qa/qa/vendor/jenkins/page/last_job_console.rb18
103 files changed, 1859 insertions, 596 deletions
diff --git a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml
index 3e83c8f0f77..052ba1c14fb 100644
--- a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml
+++ b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml
@@ -22,10 +22,12 @@ variables:
stages:
- production
-# This job continuously deploys to staging/production on every push to `master`.
+# This job continuously deploys to production on every push to `master`.
production:
stage: production
+ tags:
+ - qa
script:
- check_kube_domain
- install_dependencies
@@ -34,7 +36,6 @@ production:
- initialize_tiller
- create_secret
- deploy
- - persist_environment_url
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 771f135a95c..bd2fbbd80cb 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -12,6 +12,8 @@ module QA
module Git
class Repository
include Scenario::Actable
+ include Support::Repeater
+
RepositoryCommandError = Class.new(StandardError)
attr_writer :use_lfs, :gpg_key_id
@@ -58,8 +60,8 @@ module QA
end
def clone(opts = '')
- clone_result = run("git clone #{opts} #{uri} ./")
- return clone_result.response unless clone_result.success
+ clone_result = run("git clone #{opts} #{uri} ./", max_attempts: 3)
+ return clone_result.response unless clone_result.success?
enable_lfs_result = enable_lfs if use_lfs?
@@ -92,7 +94,7 @@ module QA
if use_lfs?
git_lfs_track_result = run(%Q{git lfs track #{name} --lockable})
- return git_lfs_track_result.response unless git_lfs_track_result.success
+ return git_lfs_track_result.response unless git_lfs_track_result.success?
end
git_add_result = run(%Q{git add #{name}})
@@ -101,11 +103,11 @@ module QA
end
def delete_tag(tag_name)
- run(%Q{git push origin --delete #{tag_name}}).to_s
+ run(%Q{git push origin --delete #{tag_name}}, max_attempts: 3).to_s
end
def commit(message)
- run(%Q{git commit -m "#{message}"}).to_s
+ run(%Q{git commit -m "#{message}"}, max_attempts: 3).to_s
end
def commit_with_gpg(message)
@@ -113,13 +115,21 @@ module QA
end
def push_changes(branch = 'master')
- run("git push #{uri} #{branch}").to_s
+ run("git push #{uri} #{branch}", max_attempts: 3).to_s
end
def merge(branch)
run("git merge #{branch}")
end
+ def init_repository
+ run("git init")
+ end
+
+ def pull(repository = nil, branch = nil)
+ run(['git', 'pull', repository, branch].compact.join(' '))
+ end
+
def commits
run('git log --oneline').to_s.split("\n")
end
@@ -164,8 +174,8 @@ module QA
def fetch_supported_git_protocol
# ls-remote is one command known to respond to Git protocol v2 so we use
# it to get output including the version reported via Git tracing
- output = run("git ls-remote #{uri}", "GIT_TRACE_PACKET=1")
- output.response[/git< version (\d+)/, 1] || 'unknown'
+ result = run("git ls-remote #{uri}", env: "GIT_TRACE_PACKET=1", max_attempts: 3)
+ result.response[/git< version (\d+)/, 1] || 'unknown'
end
def try_add_credentials_to_netrc
@@ -175,6 +185,10 @@ module QA
save_netrc_content
end
+ def file_content(file)
+ run("cat #{file}").to_s
+ end
+
private
attr_reader :uri, :username, :password, :known_hosts_file,
@@ -182,9 +196,12 @@ module QA
alias_method :use_lfs?, :use_lfs
- Result = Struct.new(:success, :response) do
- alias_method :success?, :success
+ Result = Struct.new(:command, :exitstatus, :response) do
alias_method :to_s, :response
+
+ def success?
+ exitstatus.zero?
+ end
end
def add_credentials?
@@ -209,19 +226,26 @@ module QA
touch_gitconfig_result.to_s + git_lfs_install_result.to_s
end
- def run(command_str, *extra_env)
- command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ')
- Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
+ def run(command_str, env: [], max_attempts: 1)
+ command = [env_vars, *env, command_str, '2>&1'].compact.join(' ')
+ result = nil
- output, status = Open3.capture2e(command)
- output.chomp!
- Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
+ repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
+ Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
+ output, status = Open3.capture2e(command)
+ output.chomp!
+ Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
+
+ result = Result.new(command, status.exitstatus, output)
+
+ result.success?
+ end
- unless status.success?
- raise RepositoryCommandError, "The command #{command} failed (#{status.exitstatus}) with the following output:\n#{output}"
+ unless result.success?
+ raise RepositoryCommandError, "The command #{result.command} failed (#{result.exitstatus}) with the following output:\n#{result.response}"
end
- Result.new(status.exitstatus == 0, output)
+ result
end
def default_credentials
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 42208f05c89..cb3827f8eb1 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -133,8 +133,13 @@ module QA
end
# replace with (..., page = self.class)
- def click_element(name, page = nil, text: nil, wait: Capybara.default_max_wait_time)
- find_element(name, text: text, wait: wait).click
+ def click_element(name, page = nil, **kwargs)
+ wait_for_requests
+
+ wait = kwargs.delete(:wait) || Capybara.default_max_wait_time
+ text = kwargs.delete(:text)
+
+ find(element_selector_css(name, kwargs), text: text, wait: wait).click
page.validate_elements_present! if page
end
diff --git a/qa/qa/page/component/breadcrumbs.rb b/qa/qa/page/component/breadcrumbs.rb
index 656aa380bbd..2576e376e4e 100644
--- a/qa/qa/page/component/breadcrumbs.rb
+++ b/qa/qa/page/component/breadcrumbs.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module Breadcrumbs
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/layouts/nav/_breadcrumbs.html.haml' do
element :breadcrumb_links_content
end
diff --git a/qa/qa/page/component/ci_badge_link.rb b/qa/qa/page/component/ci_badge_link.rb
index 3db675c3a60..8629399e911 100644
--- a/qa/qa/page/component/ci_badge_link.rb
+++ b/qa/qa/page/component/ci_badge_link.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Component
module CiBadgeLink
+ extend QA::Page::PageConcern
+
COMPLETED_STATUSES = %w[passed failed canceled blocked skipped manual].freeze # excludes created, pending, running
INCOMPLETE_STATUSES = %w[pending created running].freeze
@@ -27,6 +29,8 @@ module QA
end
def self.included(base)
+ super
+
base.view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do
element :status_badge
end
diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb
index fbe19e5802b..a0aea6fe44d 100644
--- a/qa/qa/page/component/clone_panel.rb
+++ b/qa/qa/page/component/clone_panel.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module ClonePanel
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/projects/buttons/_clone.html.haml' do
element :clone_dropdown
element :clone_options
diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb
index 355e2783fb7..039640d207a 100644
--- a/qa/qa/page/component/confirm_modal.rb
+++ b/qa/qa/page/component/confirm_modal.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module ConfirmModal
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/shared/_confirm_modal.html.haml' do
element :confirm_modal
element :confirm_input
diff --git a/qa/qa/page/component/custom_metric.rb b/qa/qa/page/component/custom_metric.rb
new file mode 100644
index 00000000000..094979f5e18
--- /dev/null
+++ b/qa/qa/page/component/custom_metric.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module CustomMetric
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/custom_metrics/components/custom_metrics_form_fields.vue' do
+ element :custom_metric_prometheus_title_field
+ element :custom_metric_prometheus_query_field
+ element :custom_metric_prometheus_y_label_field
+ element :custom_metric_prometheus_unit_label_field
+ element :custom_metric_prometheus_legend_label_field
+ end
+ end
+
+ def add_custom_metric
+ fill_element :custom_metric_prometheus_title_field, 'HTTP Requests Total'
+ fill_element :custom_metric_prometheus_query_field, 'rate(http_requests_total[5m])'
+ fill_element :custom_metric_prometheus_y_label_field, 'Requests/second'
+ fill_element :custom_metric_prometheus_unit_label_field, 'req/sec'
+ fill_element :custom_metric_prometheus_legend_label_field, 'HTTP requests'
+
+ save_changes
+ end
+
+ def save_changes
+ click_button(class: 'btn-success')
+ end
+
+ def delete_custom_metric
+ click_button(class: 'btn-danger')
+ within('.modal-content') { click_button(class: 'btn-danger') }
+ end
+
+ def edit_custom_metric
+ fill_element :custom_metric_prometheus_title_field, ''
+ fill_element :custom_metric_prometheus_title_field, 'Throughput'
+
+ save_changes
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb
new file mode 100644
index 00000000000..a8a24bd3949
--- /dev/null
+++ b/qa/qa/page/component/design_management.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module DesignManagement
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.class_eval do
+ view 'app/assets/javascripts/design_management/components/design_notes/design_discussion.vue' do
+ element :design_discussion_content
+ end
+
+ view 'app/assets/javascripts/design_management/components/design_notes/design_note.vue' do
+ element :note_content
+ end
+
+ view 'app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue' do
+ element :note_textarea
+ element :save_comment_button
+ end
+
+ view 'app/assets/javascripts/design_management/components/design_overlay.vue' do
+ element :design_image_button
+ end
+
+ view 'app/assets/javascripts/design_management/components/list/item.vue' do
+ element :design_file_name
+ element :design_image
+ end
+ end
+ end
+
+ def add_annotation(note)
+ click_element(:design_image_button)
+ fill_element(:note_textarea, note)
+ click_element(:save_comment_button)
+
+ # It takes a moment for the annotation to be saved.
+ # We'll check for the annotation in a test, but here we'll at least
+ # wait for the "Save comment" button to disappear
+ saved = has_no_element?(:save_comment_button)
+
+ raise ExpectationNotMet, %q(There was a problem while adding the annotation) unless saved
+ end
+
+ def add_design(design_file_path)
+ # `attach_file` doesn't seem able to find element via data attributes.
+ # It accepts a `class:` option, but that only works for class attributes
+ # It doesn't work as a CSS selector.
+ # So instead we use the name attribute as a locator
+ page.attach_file("design_file", design_file_path, make_visible: { display: 'block' })
+
+ filename = ::File.basename(design_file_path)
+
+ found = wait_until(reload: false, sleep_interval: 1) do
+ image = find_element(:design_image)
+
+ has_element?(:design_file_name, text: filename) &&
+ image["complete"] &&
+ image["naturalWidth"].to_i > 0
+ end
+
+ raise ElementNotFound, %Q(Attempted to attach design "#{filename}" but it did not appear) unless found
+ end
+
+ def click_design(filename)
+ click_element(:design_file_name, text: filename)
+ end
+
+ def has_annotation?(note)
+ within_element_by_index(:design_discussion_content, 0) do
+ has_element?(:note_content, text: note)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/groups_filter.rb b/qa/qa/page/component/groups_filter.rb
index 7eb1257db71..f82bb81a3fc 100644
--- a/qa/qa/page/component/groups_filter.rb
+++ b/qa/qa/page/component/groups_filter.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module GroupsFilter
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter
end
diff --git a/qa/qa/page/component/issuable/common.rb b/qa/qa/page/component/issuable/common.rb
index 1155d4da036..bbab1746d7a 100644
--- a/qa/qa/page/component/issuable/common.rb
+++ b/qa/qa/page/component/issuable/common.rb
@@ -5,7 +5,11 @@ module QA
module Component
module Issuable
module Common
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/assets/javascripts/issue_show/components/title.vue' do
element :edit_button
element :title, required: true
diff --git a/qa/qa/page/component/lazy_loader.rb b/qa/qa/page/component/lazy_loader.rb
index 6f74a4691ba..2123431fc55 100644
--- a/qa/qa/page/component/lazy_loader.rb
+++ b/qa/qa/page/component/lazy_loader.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module LazyLoader
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/assets/javascripts/lazy_loader.js' do
element :js_lazy_loaded
end
diff --git a/qa/qa/page/component/legacy_clone_panel.rb b/qa/qa/page/component/legacy_clone_panel.rb
index 7b4b30623a6..ebab9fd708c 100644
--- a/qa/qa/page/component/legacy_clone_panel.rb
+++ b/qa/qa/page/component/legacy_clone_panel.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module LegacyClonePanel
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/shared/_clone_panel.html.haml' do
element :clone_dropdown
element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
index 3e8ed9069ce..0e9cdd49519 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -4,7 +4,11 @@ module QA
module Page
module Component
module Note
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/assets/javascripts/notes/components/comment_form.vue' do
element :note_dropdown
element :discussion_option
diff --git a/qa/qa/page/component/project/templates.rb b/qa/qa/page/component/project/templates.rb
new file mode 100644
index 00000000000..8baf15acdff
--- /dev/null
+++ b/qa/qa/page/component/project/templates.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module QA
+ module Page::Component
+ module Project
+ module Templates
+ def use_template_for_project(project_name)
+ within find_element(:template_option_row, text: project_name) do
+ click_element :use_template_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index e667fad1dd3..b8beb64b6bd 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -18,10 +18,14 @@ module QA
end
end
- def search_and_select(item_text)
+ def search_item(item_text)
find('.select2-input').set(item_text)
wait_for_search_to_complete
+ end
+
+ def search_and_select(item_text)
+ search_item(item_text)
select_item(item_text)
end
@@ -36,7 +40,7 @@ module QA
end
def dropdown_open?
- has_css?('.select2-input')
+ find('.select2-focusser').disabled?
end
end
end
diff --git a/qa/qa/page/component/web_ide/alert.rb b/qa/qa/page/component/web_ide/alert.rb
index 0f0623d5ebf..c2903662b52 100644
--- a/qa/qa/page/component/web_ide/alert.rb
+++ b/qa/qa/page/component/web_ide/alert.rb
@@ -5,8 +5,12 @@ module QA
module Component
module WebIDE
module Alert
- def self.prepended(page)
- page.module_eval do
+ extend QA::Page::PageConcern
+
+ def self.prepended(base)
+ super
+
+ base.class_eval do
view 'app/assets/javascripts/ide/components/error_message.vue' do
element :flash_alert
end
diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb
new file mode 100644
index 00000000000..d28b8178c99
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/edit.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class Edit < Page::Base
+ view 'app/views/shared/snippets/_form.html.haml' do
+ element :submit_button
+ end
+
+ view 'app/assets/javascripts/snippets/components/edit.vue' do
+ element :submit_button
+ end
+
+ def add_to_file_content(content)
+ finished_loading?
+ text_area.set content
+ text_area.has_text?(content) # wait for changes to take effect
+ end
+
+ def save_changes
+ click_element(:submit_button)
+ wait_until { assert_no_element(:submit_button) }
+ end
+
+ private
+
+ def text_area
+ find('#editor textarea', visible: false)
+ 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 da5013e787e..d1a194ba1db 100644
--- a/qa/qa/page/dashboard/snippet/new.rb
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -5,16 +5,33 @@ module QA
module Dashboard
module Snippet
class New < Page::Base
+ view 'app/assets/javascripts/snippets/components/edit.vue' do
+ element :submit_button
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_description_edit.vue' do
+ element :snippet_description_field
+ element :description_placeholder, required: true
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
+ element :snippet_title, required: true
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do
+ element :snippet_file_name
+ end
+
view 'app/views/shared/form_elements/_description.html.haml' do
element :issuable_form_description
end
view 'app/views/shared/snippets/_form.html.haml' do
- element :description_field
+ element :snippet_description_field
element :description_placeholder
element :snippet_title
element :snippet_file_name
- element :create_snippet_button
+ element :submit_button
end
view 'app/views/projects/_zen.html.haml' do
@@ -28,7 +45,7 @@ module QA
def fill_description(description)
click_element :description_placeholder
- fill_element :description_field, description
+ fill_element :snippet_description_field, description
end
def set_visibility(visibility)
@@ -46,7 +63,8 @@ module QA
end
def click_create_snippet_button
- click_element :create_snippet_button
+ wait_until(reload: false) { !find_element(:submit_button).disabled? }
+ click_element :submit_button
end
private
diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb
index 88d6ef02d22..d43b64cd1d4 100644
--- a/qa/qa/page/dashboard/snippet/show.rb
+++ b/qa/qa/page/dashboard/snippet/show.rb
@@ -5,10 +5,17 @@ module QA
module Dashboard
module Snippet
class Show < Page::Base
+ view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do
+ element :snippet_description_field
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
+ element :snippet_title, required: true
+ end
+
view 'app/views/shared/snippets/_header.html.haml' do
element :snippet_title, required: true
- element :snippet_description, required: true
- element :embed_type
+ element :snippet_description_field, required: true
element :snippet_box
end
@@ -16,22 +23,38 @@ module QA
element :file_title_name
end
+ view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do
+ element :file_title_name
+ end
+
view 'app/views/shared/_file_highlight.html.haml' do
element :file_content
end
+ view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do
+ element :file_content
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_header.vue' do
+ element :snippet_action_button
+ element :delete_snippet_button
+ end
+
+ view 'app/assets/javascripts/snippets/components/snippet_blob_view.vue' do
+ element :clone_button
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/clone_dropdown.vue' do
+ element :copy_http_url_button
+ element :copy_ssh_url_button
+ end
+
def has_snippet_title?(snippet_title)
has_element? :snippet_title, text: snippet_title
end
def has_snippet_description?(snippet_description)
- has_element? :snippet_description, text: snippet_description
- end
-
- def has_embed_type?(embed_type)
- within_element(:embed_type) do
- has_text?(embed_type)
- end
+ has_element? :snippet_description_field, text: snippet_description
end
def has_visibility_type?(visibility_type)
@@ -52,6 +75,30 @@ module QA
has_text?(file_content)
end
end
+
+ def click_edit_button
+ finished_loading?
+ click_element(:snippet_action_button, action: 'Edit')
+ end
+
+ def click_delete_button
+ finished_loading?
+ click_element(:snippet_action_button, action: 'Delete')
+ click_element(:delete_snippet_button)
+ finished_loading? # wait for the page to reload after deletion
+ end
+
+ def get_repository_uri_http
+ finished_loading?
+ click_element(:clone_button)
+ Git::Location.new(find_element(:copy_http_url_button)['data-clipboard-text']).uri.to_s
+ end
+
+ def get_repository_uri_ssh
+ finished_loading?
+ click_element(:clone_button)
+ Git::Location.new(find_element(:copy_ssh_url_button)['data-clipboard-text']).uri.to_s
+ end
end
end
end
diff --git a/qa/qa/page/file/shared/commit_button.rb b/qa/qa/page/file/shared/commit_button.rb
index 9ea4f4e7818..c1c5907f27d 100644
--- a/qa/qa/page/file/shared/commit_button.rb
+++ b/qa/qa/page/file/shared/commit_button.rb
@@ -5,7 +5,11 @@ module QA
module File
module Shared
module CommitButton
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/projects/_commit_button.html.haml' do
element :commit_button
end
diff --git a/qa/qa/page/file/shared/commit_message.rb b/qa/qa/page/file/shared/commit_message.rb
index ce3b1e9939c..823ce7bf7f9 100644
--- a/qa/qa/page/file/shared/commit_message.rb
+++ b/qa/qa/page/file/shared/commit_message.rb
@@ -5,7 +5,11 @@ module QA
module File
module Shared
module CommitMessage
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/shared/_commit_message_container.html.haml' do
element :commit_message, "text_area_tag 'commit_message'" # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/file/shared/editor.rb b/qa/qa/page/file/shared/editor.rb
index 448c09cfbca..ce4465d2a5c 100644
--- a/qa/qa/page/file/shared/editor.rb
+++ b/qa/qa/page/file/shared/editor.rb
@@ -5,7 +5,11 @@ module QA
module File
module Shared
module Editor
+ extend QA::Page::PageConcern
+
def self.included(base)
+ super
+
base.view 'app/views/projects/blob/_editor.html.haml' do
element :editor
end
diff --git a/qa/qa/page/group/sub_menus/common.rb b/qa/qa/page/group/sub_menus/common.rb
index 96efc8da98d..86102f70d29 100644
--- a/qa/qa/page/group/sub_menus/common.rb
+++ b/qa/qa/page/group/sub_menus/common.rb
@@ -5,9 +5,12 @@ module QA
module Group
module SubMenus
module Common
+ extend QA::Page::PageConcern
include QA::Page::SubMenus::Common
def self.included(base)
+ super
+
base.class_eval do
view 'app/views/layouts/nav/sidebar/_group.html.haml' do
element :group_sidebar
diff --git a/qa/qa/page/main/terms.rb b/qa/qa/page/main/terms.rb
index a4928f24397..a0de267fb31 100644
--- a/qa/qa/page/main/terms.rb
+++ b/qa/qa/page/main/terms.rb
@@ -1,20 +1,22 @@
# frozen_string_literal: true
module QA
- module Page::Main
- class Terms < Page::Base
- view 'app/views/layouts/terms.html.haml' do
- element :user_avatar, required: true
- end
+ module Page
+ module Main
+ class Terms < Page::Base
+ view 'app/views/layouts/terms.html.haml' do
+ element :user_avatar, required: true
+ end
- view 'app/views/users/terms/index.html.haml' do
- element :terms_content, required: true
+ view 'app/views/users/terms/index.html.haml' do
+ element :terms_content, required: true
- element :accept_terms_button
- end
+ element :accept_terms_button
+ end
- def accept_terms
- click_element :accept_terms_button, Page::Main::Menu
+ def accept_terms
+ click_element :accept_terms_button, Page::Main::Menu
+ end
end
end
end
diff --git a/qa/qa/page/page_concern.rb b/qa/qa/page/page_concern.rb
new file mode 100644
index 00000000000..6ba2d27f574
--- /dev/null
+++ b/qa/qa/page/page_concern.rb
@@ -0,0 +1,16 @@
+module QA
+ module Page
+ module PageConcern
+ def included(base)
+ unless base.is_a?(Class)
+ raise "Expected #{self} to be prepended to a class, but #{base} is a module!"
+ end
+
+ unless base.ancestors.include?(::QA::Page::Base)
+ raise "Expected #{self} to be prepended to a class that inherits from ::QA::Page::Base, but #{base} doesn't!"
+ end
+ end
+ alias_method :prepended, :included
+ end
+ end
+end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 7069e7d3e4f..fd191fa3e27 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -6,9 +6,9 @@ module QA
module Page
module Profile
class PersonalAccessTokens < Page::Base
- view 'app/views/shared/_personal_access_tokens_form.html.haml' do
+ view 'app/views/shared/access_tokens/_form.html.haml' do
element :expiry_date_field
- element :personal_access_token_name_field
+ element :access_token_name_field
element :create_token_button
end
@@ -16,15 +16,15 @@ module QA
element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
- view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do
- element :created_personal_access_token
+ view 'app/views/shared/access_tokens/_created_container.html.haml' do
+ element :created_access_token
end
- view 'app/views/shared/_personal_access_tokens_table.html.haml' do
+ view 'app/views/shared/access_tokens/_table.html.haml' do
element :revoke_button
end
def fill_token_name(name)
- fill_element(:personal_access_token_name_field, name)
+ fill_element(:access_token_name_field, name)
end
def check_api
@@ -36,7 +36,7 @@ module QA
end
def created_access_token
- find_element(:created_personal_access_token, wait: 30).value
+ find_element(:created_access_token, wait: 30).value
end
def fill_expiry_date(date)
diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb
index 082202f91ca..810877e21ad 100644
--- a/qa/qa/page/profile/ssh_keys.rb
+++ b/qa/qa/page/profile/ssh_keys.rb
@@ -5,6 +5,7 @@ module QA
module Profile
class SSHKeys < Page::Base
view 'app/views/profiles/keys/_form.html.haml' do
+ element :key_expiry_date_field
element :key_title_field
element :key_public_key_field
element :add_key_button
@@ -19,17 +20,26 @@ module QA
end
def add_key(public_key, title)
- fill_element :key_public_key_field, public_key
- fill_element :key_title_field, title
+ fill_element(:key_public_key_field, public_key)
+ fill_element(:key_title_field, title)
+ # Expire in 2 days just in case the key is created just before midnight
+ fill_expiry_date(Date.today + 2)
- click_element :add_key_button
+ click_element(:add_key_button)
+ end
+
+ def fill_expiry_date(date)
+ date = date.strftime('%m/%d/%Y') if date.is_a?(Date)
+ Date.strptime(date, '%m/%d/%Y') rescue ArgumentError raise "Expiry date must be in mm/dd/yyyy format"
+
+ fill_element(:key_expiry_date_field, date)
end
def remove_key(title)
click_link(title)
accept_alert do
- click_element :delete_key_button
+ click_element(:delete_key_button)
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index b5ad63ab8de..ace2537fc0e 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -9,6 +9,15 @@ module QA
element :assignee_link
end
+ view 'app/views/projects/issues/export_csv/_button.html.haml' do
+ element :export_as_csv_button
+ end
+
+ view 'app/views/projects/issues/export_csv/_modal.html.haml' do
+ element :export_issues_button
+ element :export_issues_modal
+ end
+
view 'app/views/projects/issues/_issue.html.haml' do
element :issue
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
@@ -34,6 +43,18 @@ module QA
click_element :closed_issues_link
end
+ def click_export_as_csv_button
+ click_element(:export_as_csv_button)
+ end
+
+ def click_export_issues_button
+ click_element(:export_issues_button)
+ end
+
+ def export_issues_modal
+ find_element(:export_issues_modal)
+ end
+
def has_assignee_link_count?(count)
all_elements(:assignee_link, count: count)
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 8365ecb6348..dd74ff28763 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -7,6 +7,7 @@ module QA
class Show < Page::Base
include Page::Component::Issuable::Common
include Page::Component::Note
+ include Page::Component::DesignManagement
view 'app/assets/javascripts/notes/components/comment_form.vue' do
element :comment_button
@@ -56,6 +57,23 @@ module QA
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/projects/issues/_tabs.html.haml' do
+ element :discussion_tab_link
+ element :discussion_tab_content
+ element :designs_tab_link
+ element :designs_tab_content
+ end
+
+ def click_discussion_tab
+ click_element(:discussion_tab_link)
+ active_element?(:discussion_tab_content)
+ end
+
+ def click_designs_tab
+ click_element(:designs_tab_link)
+ active_element?(:designs_tab_content)
+ end
+
def click_milestone_link
click_element(:milestone_link)
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 26db2f20c1b..971b8c5e5f8 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -1,51 +1,55 @@
# frozen_string_literal: true
-module QA::Page
- module Project::Job
- class Show < QA::Page::Base
- include Component::CiBadgeLink
+module QA
+ module Page
+ module Project
+ module Job
+ class Show < QA::Page::Base
+ include Component::CiBadgeLink
- view 'app/assets/javascripts/jobs/components/log/log.vue' do
- element :job_log_content
- end
+ view 'app/assets/javascripts/jobs/components/log/log.vue' do
+ element :job_log_content
+ end
- view 'app/assets/javascripts/jobs/components/stages_dropdown.vue' do
- element :pipeline_path
- end
+ view 'app/assets/javascripts/jobs/components/stages_dropdown.vue' do
+ element :pipeline_path
+ end
- view 'app/assets/javascripts/jobs/components/sidebar.vue' do
- element :retry_button
- end
+ view 'app/assets/javascripts/jobs/components/sidebar.vue' do
+ element :retry_button
+ end
- def successful?(timeout: 60)
- raise "Timed out waiting for the build trace to load" unless loaded?
- raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout)
+ def successful?(timeout: 60)
+ raise "Timed out waiting for the build trace to load" unless loaded?
+ raise "Timed out waiting for the status to be a valid completed state" unless completed?(timeout: timeout)
- passed?
- end
+ passed?
+ end
- # Reminder: You may wish to wait for a particular job status before checking output
- def output(wait: 5)
- result = ''
+ # Reminder: You may wish to wait for a particular job status before checking output
+ def output(wait: 5)
+ result = ''
- wait_until(reload: false, max_duration: wait, sleep_interval: 1) do
- result = find_element(:job_log_content).text
+ wait_until(reload: false, max_duration: wait, sleep_interval: 1) do
+ result = find_element(:job_log_content).text
- result.include?('Job')
- end
+ result.include?('Job')
+ end
- result
- end
+ result
+ end
- def retry!
- click_element :retry_button
- end
+ def retry!
+ click_element :retry_button
+ end
- private
+ private
- def loaded?(wait: 60)
- wait_until(reload: true, max_duration: wait, sleep_interval: 1) do
- has_element?(:job_log_content, wait: 1)
+ def loaded?(wait: 60)
+ wait_until(reload: true, max_duration: wait, sleep_interval: 1) do
+ has_element?(:job_log_content, wait: 1)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 97214e22820..f6c015f64ea 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
+ include Page::Component::Project::Templates
view 'app/views/projects/new.html.haml' do
element :project_create_from_template_tab
@@ -26,6 +27,11 @@ module QA
element :import_github, "icon('github', text: 'GitHub')" # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/projects/project_templates/_built_in_templates.html.haml' do
+ element :use_template_button
+ element :template_option_row
+ end
+
def choose_test_namespace
choose_namespace(Runtime::Namespace.path)
end
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 84b58e9ea5b..0c92f9a9f28 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -17,6 +17,10 @@ module QA
def has_cluster?(cluster)
has_element?(:cluster, cluster_name: cluster.to_s)
end
+
+ def click_on_cluster(cluster)
+ click_on cluster.cluster_name
+ 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 020a3a1d5f8..2228cca1d3d 100644
--- a/qa/qa/page/project/operations/metrics/show.rb
+++ b/qa/qa/page/project/operations/metrics/show.rb
@@ -14,17 +14,22 @@ module QA
element :dashboards_filter_dropdown
element :environments_dropdown
element :edit_dashboard_button
- element :show_last_dropdown
+ element :range_picker_dropdown
end
view 'app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue' do
element :duplicate_dashboard_filename_field
end
- view 'app/assets/javascripts/monitoring/components/panel_type.vue' do
+ view 'app/assets/javascripts/monitoring/components/dashboard_panel.vue' do
element :prometheus_graph_widgets
element :prometheus_widgets_dropdown
element :alert_widget_menu_item
+ element :generate_chart_link_menu_item
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue' do
+ element :quick_range_item
end
def wait_for_metrics
@@ -66,9 +71,18 @@ module QA
end
def show_last(range = '8 hours')
- click_element :show_last_dropdown
- within_element :show_last_dropdown do
- click_on range
+ all_elements(:range_picker_dropdown, minimum: 1).first.click
+ click_element :quick_range_item, text: range
+ end
+
+ def copy_link_to_first_chart
+ all_elements(:prometheus_widgets_dropdown, minimum: 1).first.click
+ find_element(:generate_chart_link_menu_item)['data-clipboard-text']
+ end
+
+ def has_custom_metric?(metric)
+ within_element :prometheus_graphs do
+ has_text?(metric)
end
end
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 327eedeaf91..54e4d0fb2fc 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -1,41 +1,45 @@
# frozen_string_literal: true
-module QA::Page
- module Project::Pipeline
- class Index < QA::Page::Base
- view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
- element :pipeline_url_link
- end
-
- view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
- element :pipeline_commit_status
- element :pipeline_retry_button
- end
-
- def click_on_latest_pipeline
- all_elements(:pipeline_url_link, minimum: 1, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).first.click
- end
-
- def wait_for_latest_pipeline_success
- wait_for_latest_pipeline_status { has_text?('passed') }
- end
-
- def wait_for_latest_pipeline_completion
- wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') }
- end
-
- def wait_for_latest_pipeline_status
- wait_until(reload: false, max_duration: 360) do
- within_element_by_index(:pipeline_commit_status, 0) { yield }
- end
- end
-
- def wait_for_latest_pipeline_success_or_retry
- wait_for_latest_pipeline_completion
-
- if has_text?('failed')
- click_element :pipeline_retry_button
- wait_for_latest_pipeline_success
+module QA
+ module Page
+ module Project
+ module Pipeline
+ class Index < QA::Page::Base
+ view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
+ element :pipeline_url_link
+ end
+
+ view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do
+ element :pipeline_commit_status
+ element :pipeline_retry_button
+ end
+
+ def click_on_latest_pipeline
+ all_elements(:pipeline_url_link, minimum: 1, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).first.click
+ end
+
+ def wait_for_latest_pipeline_success
+ wait_for_latest_pipeline_status { has_text?('passed') }
+ end
+
+ def wait_for_latest_pipeline_completion
+ wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') }
+ end
+
+ def wait_for_latest_pipeline_status
+ wait_until(reload: false, max_duration: 360) do
+ within_element_by_index(:pipeline_commit_status, 0) { yield }
+ end
+ end
+
+ def wait_for_latest_pipeline_success_or_retry
+ wait_for_latest_pipeline_completion
+
+ if has_text?('failed')
+ click_element :pipeline_retry_button
+ wait_for_latest_pipeline_success
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 1003b828a32..d22dfefc096 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -1,73 +1,77 @@
# frozen_string_literal: true
-module QA::Page
- module Project::Pipeline
- class Show < QA::Page::Base
- include Component::CiBadgeLink
-
- 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
+module QA
+ module Page
+ module Project
+ module Pipeline
+ class Show < QA::Page::Base
+ include Component::CiBadgeLink
+
+ 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
- view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
- element :pipeline_graph, /class.*pipeline-graph.*/ # rubocop:disable QA/ElementWithPattern
- end
+ view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
+ element :pipeline_graph, /class.*pipeline-graph.*/ # rubocop:disable QA/ElementWithPattern
+ end
- view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
- element :job_component, /class.*ci-job-component.*/ # rubocop:disable QA/ElementWithPattern
- element :job_link
- end
+ view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
+ element :job_component, /class.*ci-job-component.*/ # rubocop:disable QA/ElementWithPattern
+ element :job_link
+ end
- view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
- element :linked_pipeline_button
- end
+ view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
+ element :linked_pipeline_button
+ end
- view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
- element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern
- end
+ view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
+ element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern
+ end
- view 'app/views/projects/pipelines/_info.html.haml' do
- element :pipeline_badges
- end
+ view 'app/views/projects/pipelines/_info.html.haml' do
+ element :pipeline_badges
+ end
- def running?(wait: 0)
- within('.ci-header-container') do
- page.has_content?('running', wait: wait)
- end
- end
+ def running?(wait: 0)
+ within('.ci-header-container') do
+ page.has_content?('running', wait: wait)
+ end
+ end
- def has_build?(name, status: :success, wait: nil)
- within('.pipeline-graph') do
- within('.ci-job-component', text: name) do
- has_selector?(".ci-status-icon-#{status}", { wait: wait }.compact)
+ def has_build?(name, status: :success, wait: nil)
+ within('.pipeline-graph') do
+ within('.ci-job-component', text: name) do
+ has_selector?(".ci-status-icon-#{status}", { wait: wait }.compact)
+ end
+ end
end
- end
- end
- def has_job?(job_name)
- has_element?(:job_link, text: job_name)
- end
+ def has_job?(job_name)
+ has_element?(:job_link, text: job_name)
+ end
- def has_no_job?(job_name)
- has_no_element?(:job_link, text: job_name)
- end
+ def has_no_job?(job_name)
+ has_no_element?(:job_link, text: job_name)
+ end
- def has_tag?(tag_name)
- within_element(:pipeline_badges) do
- has_selector?('.badge', text: tag_name)
- end
- end
+ def has_tag?(tag_name)
+ within_element(:pipeline_badges) do
+ has_selector?('.badge', text: tag_name)
+ end
+ end
- def click_job(job_name)
- click_element(:job_link, text: job_name)
- end
+ def click_job(job_name)
+ click_element(:job_link, text: job_name)
+ end
- def click_linked_job(project_name)
- click_element(:linked_pipeline_button, text: /#{project_name}/)
- end
+ def click_linked_job(project_name)
+ click_element(:linked_pipeline_button, text: /#{project_name}/)
+ end
- def click_on_first_job
- first('.js-pipeline-graph-job-link', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).click
+ def click_on_first_job
+ first('.js-pipeline-graph-job-link', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).click
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index c95c47fa560..3bb5181a31c 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -57,6 +57,10 @@ module QA
click_element :download_export_link
end
+ def has_download_export_link?
+ has_element? :download_export_link
+ end
+
def archive_project
page.accept_alert("Are you sure that you want to archive this project?") do
click_element :archive_project_link
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 46f93fad61e..aa27c030b78 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -5,12 +5,19 @@ module QA
module Project
module Settings
class CICD < Page::Base
- include Common
+ include QA::Page::Settings::Common
view 'app/views/projects/settings/ci_cd/show.html.haml' do
element :autodevops_settings_content
element :runners_settings_content
element :variables_settings_content
+ element :general_pipelines_settings_content
+ end
+
+ def expand_general_pipelines(&block)
+ expand_section(:general_pipelines_settings_content) do
+ Settings::GeneralPipelines.perform(&block)
+ end
end
def expand_runners_settings(&block)
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index 6cdf40cd1da..de268b14aa2 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class CiVariables < Page::Base
- include Common
+ include QA::Page::Settings::Common
view 'app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue' do
element :ci_variable_key_field
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
deleted file mode 100644
index f5f22623060..00000000000
--- a/qa/qa/page/project/settings/common.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Settings
- module Common
- include QA::Page::Settings::Common
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index c330d090ce6..8d655b0684e 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class DeployKeys < Page::Base
- view 'app/views/projects/deploy_keys/_form.html.haml' do
+ view 'app/views/shared/deploy_keys/_form.html.haml' do
element :deploy_key_title, 'text_field :title' # rubocop:disable QA/ElementWithPattern
element :deploy_key_key, 'text_area :key' # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/settings/general_pipelines.rb b/qa/qa/page/project/settings/general_pipelines.rb
new file mode 100644
index 00000000000..5a98849a41d
--- /dev/null
+++ b/qa/qa/page/project/settings/general_pipelines.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class GeneralPipelines < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/projects/settings/ci_cd/_form.html.haml' do
+ element :build_coverage_regex_field
+ element :save_general_pipelines_changes_button
+ end
+
+ def configure_coverage_regex(pattern)
+ fill_element :build_coverage_regex_field, pattern
+ click_element :save_general_pipelines_changes_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/incidents.rb b/qa/qa/page/project/settings/incidents.rb
new file mode 100644
index 00000000000..94d5fc369ad
--- /dev/null
+++ b/qa/qa/page/project/settings/incidents.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class Incidents < Page::Base
+ view 'app/views/projects/settings/operations/_incidents.html.haml' do
+ element :create_issue_checkbox
+ element :incident_templates_dropdown
+ element :save_changes_button
+ end
+
+ def enable_issues_for_incidents
+ check_element :create_issue_checkbox
+ end
+
+ def select_issue_template(template)
+ within_element :incident_templates_dropdown do
+ find(:option, template).select_option
+ end
+ end
+
+ def save_incident_settings
+ click_element :save_changes_button
+ end
+
+ def has_template?(template)
+ within_element :incident_templates_dropdown do
+ has_text?(template)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
new file mode 100644
index 00000000000..436a42fb093
--- /dev/null
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ 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
+ end
+
+ def click_on_prometheus_integration
+ click_element :prometheus_link
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index 18d55598d90..efae497b6ba 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class Main < Page::Base
- include Common
+ include QA::Page::Settings::Common
include Component::Select2
include SubMenus::Project
diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb
index 7da2c9d168c..0092426b31f 100644
--- a/qa/qa/page/project/settings/merge_request.rb
+++ b/qa/qa/page/project/settings/merge_request.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class MergeRequest < QA::Page::Base
- include Common
+ include QA::Page::Settings::Common
view 'app/views/projects/edit.html.haml' do
element :save_merge_request_changes
diff --git a/qa/qa/page/project/settings/operations.rb b/qa/qa/page/project/settings/operations.rb
new file mode 100644
index 00000000000..f6e005d3189
--- /dev/null
+++ b/qa/qa/page/project/settings/operations.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class Operations < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/projects/settings/operations/_incidents.html.haml' do
+ element :incidents_settings_content
+ end
+
+ def expand_incidents(&block)
+ expand_section(:incidents_settings_content) do
+ Settings::Incidents.perform(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 8810b971fda..8e9a24a4741 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class Repository < Page::Base
- include Common
+ include QA::Page::Settings::Common
view 'app/views/projects/protected_branches/shared/_index.html.haml' do
element :protected_branches_settings
@@ -19,7 +19,7 @@ module QA
element :deploy_tokens_settings
end
- view 'app/views/projects/deploy_keys/_index.html.haml' do
+ view 'app/views/shared/deploy_keys/_index.html.haml' do
element :deploy_keys_settings
end
diff --git a/qa/qa/page/project/settings/services/prometheus.rb b/qa/qa/page/project/settings/services/prometheus.rb
new file mode 100644
index 00000000000..8ae4ded535e
--- /dev/null
+++ b/qa/qa/page/project/settings/services/prometheus.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ module Services
+ class Prometheus < Page::Base
+ include Page::Component::CustomMetric
+
+ view 'app/views/projects/services/prometheus/_custom_metrics.html.haml' do
+ element :custom_metrics_container
+ element :new_metric_button
+ end
+
+ def click_on_custom_metric(custom_metric)
+ within_element :custom_metrics_container do
+ click_on custom_metric
+ end
+ end
+
+ def click_on_new_metric
+ click_element :new_metric_button
+ end
+
+ def has_custom_metric?(custom_metric)
+ within_element :custom_metrics_container do
+ has_text? custom_metric
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb
index 2f0bc8b9ba6..9405ea97fff 100644
--- a/qa/qa/page/project/sub_menus/ci_cd.rb
+++ b/qa/qa/page/project/sub_menus/ci_cd.rb
@@ -5,10 +5,14 @@ module QA
module Project
module SubMenus
module CiCd
- include Page::Project::SubMenus::Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :link_pipelines
end
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
index da759398cff..85bf932be4a 100644
--- a/qa/qa/page/project/sub_menus/common.rb
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -5,6 +5,7 @@ module QA
module Project
module SubMenus
module Common
+ extend QA::Page::PageConcern
include QA::Page::SubMenus::Common
private
diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb
index d27a250a300..c15a8ec4cc7 100644
--- a/qa/qa/page/project/sub_menus/issues.rb
+++ b/qa/qa/page/project/sub_menus/issues.rb
@@ -5,10 +5,14 @@ module QA
module Project
module SubMenus
module Issues
- include Page::Project::SubMenus::Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :issue_boards_link
element :issues_item
diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb
index bcbc1dc16d3..ff9c8a21174 100644
--- a/qa/qa/page/project/sub_menus/operations.rb
+++ b/qa/qa/page/project/sub_menus/operations.rb
@@ -5,12 +5,16 @@ module QA
module Project
module SubMenus
module Operations
- include Page::Project::SubMenus::Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
- element :link_operations
+ element :operations_link
element :operations_environments_link
element :operations_metrics_link
end
@@ -45,8 +49,8 @@ module QA
def hover_operations
within_sidebar do
- scroll_to_element(:link_operations)
- find_element(:link_operations).hover
+ scroll_to_element(:operations_link)
+ find_element(:operations_link).hover
yield
end
diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb
index 6f1bc131f84..4af640301b9 100644
--- a/qa/qa/page/project/sub_menus/project.rb
+++ b/qa/qa/page/project/sub_menus/project.rb
@@ -5,10 +5,14 @@ module QA
module Project
module SubMenus
module Project
- include Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :project_link
end
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
index 65149e631f3..38d6b8e50f4 100644
--- a/qa/qa/page/project/sub_menus/repository.rb
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -5,10 +5,14 @@ module QA
module Project
module SubMenus
module Repository
- include Page::Project::SubMenus::Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :project_menu_repo
element :branches_link
@@ -44,5 +48,3 @@ module QA
end
end
end
-
-QA::Page::Project::SubMenus::Repository.prepend_if_ee('QA::EE::Page::Project::SubMenus::Repository')
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index 8be442ba35d..0dd4bd1817a 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -5,15 +5,20 @@ module QA
module Project
module SubMenus
module Settings
- include Page::Project::SubMenus::Common
+ extend QA::Page::PageConcern
def self.included(base)
+ super
+
base.class_eval do
+ include QA::Page::Project::SubMenus::Common
+
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
end
end
end
@@ -64,6 +69,14 @@ module QA
end
end
+ def go_to_operations_settings
+ hover_settings do
+ within_submenu do
+ click_element :operations_settings_link
+ end
+ end
+ end
+
private
def hover_settings
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
index 85f1d224935..55477db8804 100644
--- a/qa/qa/page/search/results.rb
+++ b/qa/qa/page/search/results.rb
@@ -1,53 +1,55 @@
# frozen_string_literal: true
-module QA::Page
- module Search
- class Results < QA::Page::Base
- view 'app/views/search/_category.html.haml' do
- element :code_tab
- element :projects_tab
- end
+module QA
+ module Page
+ module Search
+ class Results < QA::Page::Base
+ view 'app/views/search/_category.html.haml' do
+ element :code_tab
+ element :projects_tab
+ end
- view 'app/views/search/results/_blob_data.html.haml' do
- element :result_item_content
- element :file_title_content
- element :file_text_content
- end
+ view 'app/views/search/results/_blob_data.html.haml' do
+ element :result_item_content
+ element :file_title_content
+ element :file_text_content
+ end
- view 'app/views/shared/projects/_project.html.haml' do
- element :project
- end
+ view 'app/views/shared/projects/_project.html.haml' do
+ element :project
+ end
- def switch_to_code
- switch_to_tab(:code_tab)
- end
+ def switch_to_code
+ switch_to_tab(:code_tab)
+ end
- def switch_to_projects
- switch_to_tab(:projects_tab)
- end
+ def switch_to_projects
+ switch_to_tab(:projects_tab)
+ end
- def has_file_in_project?(file_name, project_name)
- has_element?(:result_item_content, text: "#{project_name}: #{file_name}")
- end
+ def has_file_in_project?(file_name, project_name)
+ has_element?(:result_item_content, text: "#{project_name}: #{file_name}")
+ end
- def has_file_with_content?(file_name, file_text)
- within_element_by_index(:result_item_content, 0) do
- break false unless has_element?(:file_title_content, text: file_name)
+ def has_file_with_content?(file_name, file_text)
+ within_element_by_index(:result_item_content, 0) do
+ break false unless has_element?(:file_title_content, text: file_name)
- has_element?(:file_text_content, text: file_text)
+ has_element?(:file_text_content, text: file_text)
+ end
end
- end
- def has_project?(project_name)
- has_element?(:project, project_name: project_name)
- end
+ def has_project?(project_name)
+ has_element?(:project, project_name: project_name)
+ end
- private
+ private
- def switch_to_tab(tab)
- retry_until do
- click_element(tab)
- has_active_element?(tab)
+ def switch_to_tab(tab)
+ retry_until do
+ click_element(tab)
+ has_active_element?(tab)
+ end
end
end
end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index cac58c599ea..591aa449219 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -14,6 +14,7 @@ module QA
ResourceQueryError = Class.new(RuntimeError)
ResourceUpdateFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
+ InternalServerError = Class.new(RuntimeError)
attr_reader :api_resource, :api_response
attr_writer :api_client
@@ -54,11 +55,23 @@ module QA
end
end
- private
-
include Support::Api
attr_writer :api_resource, :api_response
+ def api_put(body = api_put_body)
+ response = put(
+ Runtime::API::Request.new(api_client, api_put_path).url,
+ body)
+
+ unless response.code == HTTP_STATUS_OK
+ raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
+ end
+
+ process_api_response(parse_body(response))
+ end
+
+ private
+
def resource_web_url(resource)
resource.fetch(:web_url) do
raise ResourceURLMissingError, "API resource for #{self.class.name} does not expose a `web_url` property: `#{resource}`."
@@ -73,7 +86,9 @@ module QA
url = Runtime::API::Request.new(api_client, get_path).url
response = get(url)
- unless response.code == HTTP_STATUS_OK
+ if response.code == HTTP_STATUS_SERVER_ERROR
+ raise InternalServerError, "Failed to GET #{url} - (#{response.code}): `#{response}`."
+ elsif response.code != HTTP_STATUS_OK
raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`."
end
@@ -92,18 +107,6 @@ module QA
process_api_response(parse_body(response))
end
- def api_put
- response = put(
- Runtime::API::Request.new(api_client, api_put_path).url,
- api_put_body)
-
- unless response.code == HTTP_STATUS_OK
- raise ResourceFabricationFailedError, "Updating #{self.class.name} using the API failed (#{response.code}) with `#{response}`."
- end
-
- process_api_response(parse_body(response))
- end
-
def api_delete
url = Runtime::API::Request.new(api_client, api_delete_path).url
response = delete(url)
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
deleted file mode 100644
index 7306acfe2a4..00000000000
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-require 'securerandom'
-
-module QA
- module Resource
- class KubernetesCluster < Base
- attr_writer :project, :cluster,
- :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner, :domain
-
- attribute :ingress_ip do
- Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
- end
-
- def fabricate!
- @project.visit!
-
- Page::Project::Menu.perform(
- &:go_to_operations_kubernetes)
-
- Page::Project::Operations::Kubernetes::Index.perform(
- &:add_kubernetes_cluster)
-
- Page::Project::Operations::Kubernetes::Add.perform(
- &:add_existing_cluster)
-
- Page::Project::Operations::Kubernetes::AddExisting.perform do |cluster_page|
- cluster_page.set_cluster_name(@cluster.cluster_name)
- cluster_page.set_api_url(@cluster.api_url)
- cluster_page.set_ca_certificate(@cluster.ca_certificate)
- cluster_page.set_token(@cluster.token)
- cluster_page.uncheck_rbac! unless @cluster.rbac
- cluster_page.add_cluster!
- end
-
- if @install_helm_tiller
- Page::Project::Operations::Kubernetes::Show.perform do |show|
- # We must wait a few seconds for permissions to be set up correctly for new cluster
- sleep 10
-
- # Open applications tab
- show.open_applications
-
- # Helm must be installed before everything else
- show.install!(:helm)
- show.await_installed(:helm)
-
- show.install!(:ingress) if @install_ingress
- show.install!(:prometheus) if @install_prometheus
- show.install!(:runner) if @install_runner
-
- show.await_installed(:ingress) if @install_ingress
- show.await_installed(:prometheus) if @install_prometheus
- show.await_installed(:runner) if @install_runner
-
- if @install_ingress
- populate(:ingress_ip)
-
- show.open_details
- show.set_domain("#{ingress_ip}.nip.io")
- show.save_domain
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/resource/kubernetes_cluster/base.rb b/qa/qa/resource/kubernetes_cluster/base.rb
new file mode 100644
index 00000000000..38bca48be17
--- /dev/null
+++ b/qa/qa/resource/kubernetes_cluster/base.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'securerandom'
+
+module QA
+ module Resource
+ module KubernetesCluster
+ class Base < Resource::Base
+ attr_writer :add_name_uuid
+
+ attribute :id
+ attribute :name
+ attribute :domain
+ attribute :enabled
+ attribute :managed
+ attribute :management_project_id
+
+ attribute :api_url
+ attribute :token
+ attribute :ca_cert
+ attribute :namespace
+
+ attribute :authorization_type
+ attribute :environment_scope
+
+ def initialize
+ @add_name_uuid = true
+ @enabled = true
+ @managed = true
+ @authorization_type = :rbac
+ @environment_scope = :*
+ end
+
+ def name=(new_name)
+ @name = @add_name_uuid ? "#{new_name}-#{SecureRandom.hex(5)}" : new_name
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/kubernetes_cluster/project_cluster.rb b/qa/qa/resource/kubernetes_cluster/project_cluster.rb
new file mode 100644
index 00000000000..5c61cc29236
--- /dev/null
+++ b/qa/qa/resource/kubernetes_cluster/project_cluster.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module KubernetesCluster
+ class ProjectCluster < Base
+ attr_writer :cluster,
+ :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner, :domain
+
+ attribute :project do
+ Resource::Project.fabricate!
+ end
+
+ attribute :ingress_ip do
+ Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform(
+ &:go_to_operations_kubernetes)
+
+ Page::Project::Operations::Kubernetes::Index.perform(
+ &:add_kubernetes_cluster)
+
+ Page::Project::Operations::Kubernetes::Add.perform(
+ &:add_existing_cluster)
+
+ Page::Project::Operations::Kubernetes::AddExisting.perform do |cluster_page|
+ cluster_page.set_cluster_name(@cluster.cluster_name)
+ cluster_page.set_api_url(@cluster.api_url)
+ cluster_page.set_ca_certificate(@cluster.ca_certificate)
+ cluster_page.set_token(@cluster.token)
+ cluster_page.uncheck_rbac! unless @cluster.rbac
+ cluster_page.add_cluster!
+ end
+
+ if @install_helm_tiller
+ Page::Project::Operations::Kubernetes::Show.perform do |show|
+ # We must wait a few seconds for permissions to be set up correctly for new cluster
+ sleep 10
+
+ # Open applications tab
+ show.open_applications
+
+ # Helm must be installed before everything else
+ show.install!(:helm)
+ show.await_installed(:helm)
+
+ show.install!(:ingress) if @install_ingress
+ show.install!(:prometheus) if @install_prometheus
+ show.install!(:runner) if @install_runner
+
+ show.await_installed(:ingress) if @install_ingress
+ show.await_installed(:prometheus) if @install_prometheus
+ show.await_installed(:runner) if @install_runner
+
+ if @install_ingress
+ populate(:ingress_ip)
+
+ show.open_details
+ show.set_domain("#{ingress_ip}.nip.io")
+ show.save_domain
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/pipeline.rb b/qa/qa/resource/pipeline.rb
new file mode 100644
index 00000000000..a115de3e825
--- /dev/null
+++ b/qa/qa/resource/pipeline.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class Pipeline < Base
+ attribute :project do
+ Resource::Project.fabricate! do |project|
+ project.name = 'project-with-pipeline'
+ end
+ end
+
+ attribute :id
+ attribute :status
+ attribute :ref
+ attribute :sha
+
+ # array in form
+ # [
+ # { key: 'UPLOAD_TO_S3', variable_type: 'file', value: true },
+ # { key: 'SOMETHING', variable_type: 'env_var', value: 'yes' }
+ # ]
+ attribute :variables
+
+ def initialize
+ @ref = 'master'
+ @variables = []
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.perform(&:click_ci_cd_pipelines)
+ Page::Project::Pipeline::Index.perform(&:click_run_pipeline_button)
+ Page::Project::Pipeline::New.perform(&:click_run_pipeline_button)
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/pipelines/#{id}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/pipeline"
+ end
+
+ def api_post_body
+ {
+ ref: ref,
+ variables: variables
+ }
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index cb047f81d02..78e2ba8a248 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -7,11 +7,11 @@ module QA
class Project < Base
include Events::Project
include Members
+ include Visibility
attr_accessor :repository_storage # requires admin access
attr_writer :initialize_with_readme
attr_writer :auto_devops_enabled
- attr_writer :visibility
attribute :id
attribute :name
@@ -19,6 +19,8 @@ module QA
attribute :description
attribute :standalone
attribute :runners_token
+ attribute :visibility
+ attribute :template_name
attribute :group do
Group.fabricate!
@@ -50,7 +52,8 @@ module QA
@description = 'My awesome project'
@initialize_with_readme = false
@auto_devops_enabled = false
- @visibility = 'public'
+ @visibility = :public
+ @template_name = nil
end
def name=(raw_name)
@@ -63,6 +66,13 @@ module QA
Page::Group::Show.perform(&:go_to_new_project)
end
+ if @template_name
+ 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::New.perform do |new_page|
new_page.choose_test_namespace
new_page.choose_name(@name)
@@ -83,6 +93,10 @@ module QA
"/projects/#{CGI.escape(path_with_namespace)}"
end
+ def api_visibility_path
+ "/projects/#{id}"
+ end
+
def api_get_archive_path(type = 'tar.gz')
"#{api_get_path}/repository/archive.#{type}"
end
@@ -95,6 +109,10 @@ module QA
"#{api_get_path}/runners"
end
+ def api_pipelines_path
+ "#{api_get_path}/pipelines"
+ end
+
def api_put_path
"/projects/#{id}"
end
@@ -118,6 +136,7 @@ module QA
end
post_body[:repository_storage] = repository_storage if repository_storage
+ post_body[:template_name] = @template_name if @template_name
post_body
end
@@ -152,10 +171,19 @@ module QA
end
def runners(tag_list: nil)
- response = get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url
+ response = if tag_list
+ get Runtime::API::Request.new(api_client, "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}").url
+ else
+ get Runtime::API::Request.new(api_client, "#{api_runners_path}").url
+ end
+
parse_body(response)
end
+ def pipelines
+ parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url))
+ end
+
def share_with_group(invitee, access_level = Resource::Members::AccessLevel::DEVELOPER)
post Runtime::API::Request.new(api_client, "/projects/#{id}/share").url, { group_id: invitee.id, group_access: access_level }
end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index f1f72c9cacd..b2a36f92ffe 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -5,8 +5,8 @@ require 'securerandom'
module QA
module Resource
class Runner < Base
- attr_writer :name, :tags, :image
- attr_accessor :config, :token
+ attr_writer :name, :tags, :image, :executor, :executor_image
+ attr_accessor :config, :token, :run_untagged
attribute :id
attribute :project do
@@ -20,35 +20,42 @@ module QA
@name || "qa-runner-#{SecureRandom.hex(4)}"
end
- def tags
- @tags || %w[qa e2e]
- end
-
def image
@image || 'gitlab/gitlab-runner:alpine'
end
+ def executor
+ @executor || :shell
+ end
+
+ def executor_image
+ @executor_image || 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6'
+ end
+
def fabricate_via_api!
Service::DockerRun::GitlabRunner.new(name).tap do |runner|
runner.pull
runner.token = @token ||= project.runners_token
runner.address = Runtime::Scenario.gitlab_address
- runner.tags = tags
+ runner.tags = @tags if @tags
runner.image = image
runner.config = config if config
+ runner.executor = executor
+ runner.executor_image = executor_image if executor == :docker
+ runner.run_untagged = run_untagged if run_untagged
runner.register!
end
end
def remove_via_api!
- runners = project.runners(tag_list: tags)
+ runners = project.runners(tag_list: @tags)
unless runners && !runners.empty?
- raise "Project #{project.path_with_namespace} has no runners with tags #{tags}."
+ raise "Project #{project.path_with_namespace} has no runners#{" with tags #{@tags}." if @tags&.any?}"
end
this_runner = runners.find { |runner| runner[:description] == name }
unless this_runner
- raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} and tags #{tags}. Runners available: #{runners}"
+ raise "Project #{project.path_with_namespace} does not have a runner with a description matching #{name} #{"or tags #{@tags}" if @tags&.any?}. Runners available: #{runners}"
end
@id = this_runner[:id]
diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb
index 3e130aef9e4..b948bf3969b 100644
--- a/qa/qa/resource/ssh_key.rb
+++ b/qa/qa/resource/ssh_key.rb
@@ -5,12 +5,16 @@ module QA
class SSHKey < Base
extend Forwardable
- attr_accessor :title
+ attr_reader :title
attribute :id
def_delegators :key, :private_key, :public_key, :md5_fingerprint
+ def initialize
+ self.title = Time.now.to_f
+ end
+
def key
@key ||= Runtime::Key::RSA.new
end
@@ -28,6 +32,10 @@ module QA
api_post
end
+ def title=(title)
+ @title = "E2E test key: #{title}"
+ end
+
def api_delete
QA::Runtime::Logger.debug("Deleting SSH key with title '#{title}' and fingerprint '#{md5_fingerprint}'")
diff --git a/qa/qa/resource/visibility.rb b/qa/qa/resource/visibility.rb
new file mode 100644
index 00000000000..b31bd3fca49
--- /dev/null
+++ b/qa/qa/resource/visibility.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Visibility
+ def set_visibility(visibility)
+ put Runtime::API::Request.new(api_client, api_visibility_path).url, { visibility: visibility }
+ end
+
+ class VisibilityLevel
+ %i(public internal private).each do |level|
+ const_set(level.upcase, level)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index b9a3c9184aa..d29571df981 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require 'airborne'
-
module QA
module Runtime
module API
diff --git a/qa/qa/runtime/project.rb b/qa/qa/runtime/project.rb
deleted file mode 100644
index 89edfee1fbe..00000000000
--- a/qa/qa/runtime/project.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Runtime
- module Project
- extend self
- extend Support::Api
-
- def create_project(project_name, api_client, project_description = 'default')
- project = Resource::Project.fabricate_via_api! do |project|
- project.add_name_uuid = false
- project.name = project_name
- project.description = project_description
- project.api_client = api_client
- project.visibility = 'public'
- end
- project
- end
-
- def push_file_to_project(target_project, file_name, file_content)
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = target_project
- push.file_name = file_name
- push.file_content = file_content
- end
- end
-
- def set_project_visibility(api_client, project_id, visibility)
- request = Runtime::API::Request.new(api_client, "/projects/#{project_id}")
- response = put request.url, visibility: visibility
- response.code.equal?(QA::Support::Api::HTTP_STATUS_OK)
- end
- end
- end
-end
diff --git a/qa/qa/scenario/test/integration/gitaly_ha.rb b/qa/qa/scenario/test/integration/gitaly_ha.rb
new file mode 100644
index 00000000000..dbca1a1dd6d
--- /dev/null
+++ b/qa/qa/scenario/test/integration/gitaly_ha.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class GitalyHA < Test::Instance::All
+ tags :gitaly_ha
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 6856a5a8399..834f6b430ac 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -6,14 +6,21 @@ module QA
module Service
module DockerRun
class GitlabRunner < Base
- attr_accessor :token, :address, :tags, :image, :run_untagged
- attr_writer :config
+ attr_reader :tags
+ attr_accessor :token, :address, :image, :run_untagged
+ attr_writer :config, :executor, :executor_image
+
+ CONFLICTING_VARIABLES_MESSAGE = <<~MSG
+ There are conflicting options preventing the runner from starting.
+ %s cannot be specified if %s is %s
+ MSG
def initialize(name)
@image = 'gitlab/gitlab-runner:alpine'
@name = name || "qa-runner-#{SecureRandom.hex(4)}"
- @tags = %w[qa test]
- @run_untagged = false
+ @run_untagged = true
+ @executor = :shell
+ @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6'
super()
end
@@ -32,23 +39,49 @@ module QA
shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh
--network #{network} --name #{@name}
- -p 8093:8093
- -e CI_SERVER_URL=#{@address}
- -e REGISTER_NON_INTERACTIVE=true
- -e REGISTRATION_TOKEN=#{@token}
- -e RUNNER_EXECUTOR=shell
- -e RUNNER_TAG_LIST=#{@tags.join(',')}
- -e RUNNER_NAME=#{@name}
+ #{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker}
+ --privileged
#{@image} -c "#{register_command}"
CMD
end
+ def tags=(tags)
+ @tags = tags
+ @run_untagged = false
+ end
+
private
def register_command
- <<~CMD
+ args = []
+ args << '--non-interactive'
+ args << "--name #{@name}"
+ args << "--url #{@address}"
+ args << "--registration-token #{@token}"
+
+ args << if run_untagged
+ raise CONFLICTING_VARIABLES_MESSAGE % [:tags=, :run_untagged, run_untagged] if @tags&.any?
+
+ '--run-untagged=true'
+ else
+ raise 'You must specify tags to run!' unless @tags&.any?
+
+ "--tag-list #{@tags.join(',')}"
+ end
+
+ args << "--executor #{@executor}"
+
+ if @executor == :docker
+ args << "--docker-image #{@executor_image}"
+ args << '--docker-tlsverify=false'
+ args << '--docker-privileged=true'
+ args << "--docker-network-mode=#{network}"
+ end
+
+ <<~CMD.strip
printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml &&
- gitlab-runner register --run-untagged=#{@run_untagged} &&
+ gitlab-runner register \
+ #{args.join(' ')} &&
gitlab-runner run
CMD
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
new file mode 100644
index 00000000000..d8fa72456ad
--- /dev/null
+++ b/qa/qa/service/praefect_manager.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ class PraefectManager
+ include Service::Shellout
+
+ def initialize
+ @praefect = 'praefect'
+ @first_node = 'gitaly1'
+ @second_node = 'gitaly2'
+ @primary_node = @first_node
+ @secondary_node = @second_node
+ end
+
+ def stop_primary_node
+ shell "docker stop #{@primary_node}"
+ @secondary_node, @primary_node = @primary_node, @secondary_node
+ end
+
+ def reset
+ shell "docker start #{@primary_node}"
+ shell "docker start #{@secondary_node}"
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
index 819739ac535..1bf435014af 100644
--- a/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'airborne'
+
module QA
context 'Manage with IP rate limits', :requires_admin do
describe 'Users API' do
diff --git a/qa/qa/specs/features/api/1_manage/users_spec.rb b/qa/qa/specs/features/api/1_manage/users_spec.rb
index ba1ba204d24..fbc26e81b69 100644
--- a/qa/qa/specs/features/api/1_manage/users_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/users_spec.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'airborne'
+
module QA
context 'Manage' do
describe 'Users API' do
diff --git a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
index f14fcc5afce..58d716f759e 100644
--- a/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
+++ b/qa/qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'airborne'
+
module QA
context 'Plan' do
include Support::Api
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index dc471128dae..92858ba4107 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'airborne'
require 'securerandom'
module QA
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index 5ba434a7781..3ad56e21ad4 100644
--- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require 'airborne'
require 'securerandom'
require 'digest'
diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
index 5f7a6981f23..0a577aa07f8 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
@@ -3,6 +3,8 @@
module QA
context 'Plan', :orchestrated, :smtp do
describe 'Email Notification' do
+ include Support::Api
+
let(:user) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
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 2cb6a76b6b3..7b4418191a3 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -17,7 +17,7 @@ module QA
end
end
- context 'when using attachments in comments', :object_storage, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/205408', type: :bug } do
+ context 'when using attachments in comments', :object_storage do
let(:gif_file_name) { 'banana_sample.gif' }
let(:file_to_attach) do
File.absolute_path(File.join('spec', 'fixtures', gif_file_name))
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
new file mode 100644
index 00000000000..409c7d321f0
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan', :reliable do
+ describe 'Issue board focus mode' do
+ let(:project) do
+ QA::Resource::Project.fabricate_via_api! do |project|
+ project.name = 'sample-project-issue-board-focus-mode'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'focuses on issue board' do
+ project.visit!
+
+ Page::Project::Menu.perform(&:go_to_boards)
+ EE::Page::Component::IssueBoard::Show.perform do |show|
+ show.click_focus_mode_button
+
+ expect(show.focused_board).to be_visible
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_add_annotation.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_add_annotation.rb
new file mode 100644
index 00000000000..b50edcfcf08
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_add_annotation.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Design management' do
+ let(:issue) { Resource::Issue.fabricate_via_api! }
+ let(:design_filename) { 'banana_sample.gif' }
+ let(:design) { File.absolute_path(File.join('spec', 'fixtures', design_filename)) }
+ let(:annotation) { "This design is great!" }
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ it 'user adds a design and annotation' do
+ issue.visit!
+
+ Page::Project::Issue::Show.perform do |show|
+ show.click_designs_tab
+ show.add_design(design)
+ show.click_design(design_filename)
+ show.add_annotation(annotation)
+
+ expect(show).to have_annotation(annotation)
+
+ show.click_discussion_tab
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb
new file mode 100644
index 00000000000..3bb03f68d51
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/gitaly/high_availability_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ context 'Gitaly' do
+ describe 'High Availability', :orchestrated, :gitaly_ha do
+ let(:project) do
+ Resource::Project.fabricate! do |project|
+ project.name = 'gitaly_high_availability'
+ end
+ end
+ let(:initial_file) { 'pushed_to_primary.txt' }
+ let(:final_file) { 'pushed_to_secondary.txt' }
+
+ before do
+ @praefect_manager = Service::PraefectManager.new
+ Flow::Login.sign_in
+ end
+
+ after do
+ @praefect_manager.reset
+ end
+
+ it 'makes sure that automatic failover is happening' do
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.commit_message = 'pushed to primary gitaly node'
+ push.new_branch = true
+ push.file_name = initial_file
+ push.file_content = "This should exist on both nodes"
+ end
+
+ @praefect_manager.stop_primary_node
+
+ project.visit!
+
+ Page::Project::Show.perform do |show|
+ show.wait_until do
+ show.has_name?(project.name)
+ end
+ expect(show).to have_file(initial_file)
+ end
+
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.add_files([
+ {
+ file_path: 'committed_to_primary.txt',
+ content: 'This should exist on both nodes too'
+ }
+ ])
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |show|
+ expect(show).to have_file(final_file)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index 7d4e6b7efbc..3964ae7eada 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -10,6 +10,8 @@ module QA
merge_request.fork_branch = 'feature-branch'
end
+ merge_request.project.api_put(auto_devops_enabled: false)
+
Page::Main::Menu.perform(&:sign_out)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index 25866e12185..68bbc1719fc 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -12,7 +12,7 @@ module QA
resource.title = key_title
end
- expect(page).to have_content("Title: #{key_title}")
+ expect(page).to have_content(key.title)
expect(page).to have_content(key.md5_fingerprint)
Page::Main::Menu.perform(&:click_settings_link)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
index 13fe8918f97..d0123da53bb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/move_project_create_fork_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
- describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin, quarantine: { type: :new } do
+ context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217002', type: :investigating } do
+ describe 'Gitaly repository storage', :orchestrated, :repository_storage, :requires_admin do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:parent_project) do
Resource::Project.fabricate_via_api! do |project|
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 9bc4dcbca2a..9b504ad76b4 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,8 @@
module QA
context 'Create', :requires_admin do
describe 'push after setting the file size limit via admin/application_settings' do
+ include Support::Api
+
before(:context) do
@project = Resource::Project.fabricate_via_api! do |p|
p.name = 'project-test-push-limit'
@@ -39,12 +41,10 @@ module QA
def set_file_size_limit(limit)
request = Runtime::API::Request.new(@api_client, '/application/settings')
- put request.url, receive_max_input_size: limit
+ response = put request.url, receive_max_input_size: limit
- expect_status(200)
- expect(json_body).to match(
- a_hash_including(receive_max_input_size: limit)
- )
+ expect(response.code).to eq(200)
+ expect(parse_body(response)[:receive_max_input_size]).to eq(limit)
end
def push_new_file(file_name, wait_for_push: true)
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
new file mode 100644
index 00000000000..341ff39fdf1
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Version control for personal 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::Snippet.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 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 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_snippet_spec.rb
index d38b8560a38..dfcbf4b44c8 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_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', :smoke, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/215031', type: :investigating } do
+ context 'Create', :smoke do
describe 'Snippet creation' do
it 'User creates a snippet' do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
index 8ea1534492c..ed988bdf046 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', quarantine: { type: :new } do
+ context 'Create' do
describe 'Review a merge request in Web IDE' do
let(:new_file) { 'awesome_new_file.txt' }
let(:original_text) { 'Text' }
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 2550ee7193b..f8a589ad93b 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -7,6 +7,7 @@ module QA
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = executor
+ runner.tags = ['e2e-test']
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
new file mode 100644
index 00000000000..4d549dde858
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Verify', :docker, :runner do
+ describe 'Code coverage statistics' do
+ let(:simplecov) { '\(\d+.\d+\%\) covered' }
+ let(:executor) { "qa-runner-#{Time.now.to_i}" }
+ let(:runner) do
+ Resource::Runner.fabricate_via_api! do |runner|
+ runner.name = executor
+ runner.tags = ['e2e-test']
+ end
+ end
+
+ let(:merge_request) do
+ Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.project = runner.project
+ mr.file_name = '.gitlab-ci.yml'
+ mr.file_content = <<~EOF
+ test:
+ tags: [e2e-test]
+ script:
+ - echo '(66.67%) covered'
+ EOF
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ runner.remove_via_api!
+ end
+
+ it 'creates an MR with code coverage statistics' do
+ runner.project.visit!
+ configure_code_coverage(simplecov)
+ merge_request.visit!
+
+ Page::MergeRequest::Show.perform do |mr_widget|
+ Support::Retrier.retry_until(max_attempts: 5, sleep_interval: 5) do
+ mr_widget.has_pipeline_status?(/Pipeline #\d+ passed/)
+ end
+ expect(mr_widget).to have_content('Coverage 66.67%')
+ end
+ end
+ end
+
+ private
+
+ def configure_code_coverage(coverage_tool_pattern)
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
+ Page::Project::Settings::CICD.perform do |settings|
+ settings.expand_general_pipelines do |coverage|
+ coverage.configure_coverage_regex(coverage_tool_pattern)
+ 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 e71212bcb68..b1eb26f0d63 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release', :docker, quarantine: { type: :new } do
+ context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
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 633af9c2e8a..c9a61fc6305 100644
--- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Release', :docker, quarantine: { type: :new } do
+ context 'Release', :docker, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217250', type: :investigating } do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 0a52b01af03..292fc40bec4 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -35,7 +35,7 @@ module QA
end
# Connect K8s cluster
- Resource::KubernetesCluster.fabricate! do |k8s_cluster|
+ Resource::KubernetesCluster::ProjectCluster.fabricate! do |k8s_cluster|
k8s_cluster.project = project
k8s_cluster.cluster = cluster
k8s_cluster.install_helm_tiller = true
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 9a52109c8cb..04c68598239 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Configure' do
- describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, quarantine: { type: :new } do
+ 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 'Project Clusters' do
let(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! }
let(:project) do
@@ -21,12 +21,10 @@ module QA
end
it 'can create and associate a project cluster', :smoke do
- Resource::KubernetesCluster.fabricate_via_browser_ui! do |k8s_cluster|
+ Resource::KubernetesCluster::ProjectCluster.fabricate_via_browser_ui! do |k8s_cluster|
k8s_cluster.project = project
k8s_cluster.cluster = cluster
- end
-
- project.visit!
+ end.project.visit!
Page::Project::Menu.perform(&:go_to_operations_kubernetes)
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
new file mode 100644
index 00000000000..45273655bb6
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb
@@ -0,0 +1,155 @@
+# 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
+ before :all do
+ Flow::Login.sign_in
+ @project, @runner = deploy_project_with_prometheus
+ end
+
+ before do
+ Flow::Login.sign_in_unless_signed_in
+ @project.visit!
+ end
+
+ after :all do
+ @runner.remove_via_api!
+ @cluster.remove!
+ end
+
+ it 'configures custom metrics' do
+ verify_add_custom_metric
+ verify_edit_custom_metric
+ verify_delete_custom_metric
+ end
+
+ 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
+
+ expect(dashboard).to have_metrics
+ expect(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
+
+ expect(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
+
+ dashboard.show_last('3 hours')
+ expect(dashboard).to have_metrics
+
+ dashboard.show_last('1 day')
+ expect(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'
+ end
+
+ runner = Resource::Runner.fabricate_via_api! do |runner|
+ runner.project = project
+ runner.name = project.name
+ end
+
+ @cluster = Service::KubernetesCluster.new.create!
+
+ cluster_props = Resource::KubernetesCluster::ProjectCluster.fabricate! do |cluster_settings|
+ cluster_settings.project = project
+ cluster_settings.cluster = @cluster
+ cluster_settings.install_helm_tiller = 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.directory = Pathname
+ .new(__dir__)
+ .join('../../../../fixtures/monitored_auto_devops')
+ 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)
+
+ [project, runner]
+ end
+
+ def verify_add_custom_metric
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ Page::Project::Settings::Integrations.perform(&:click_on_prometheus_integration)
+
+ Page::Project::Settings::Services::Prometheus.perform do |metrics_panel|
+ metrics_panel.click_on_new_metric
+ metrics_panel.add_custom_metric
+ end
+
+ 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')
+ end
+ end
+
+ def verify_edit_custom_metric
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ Page::Project::Settings::Integrations.perform(&:click_on_prometheus_integration)
+ Page::Project::Settings::Services::Prometheus.perform do |metrics_panel|
+ metrics_panel.click_on_custom_metric('Business / HTTP Requests Total (req/sec)')
+ metrics_panel.edit_custom_metric
+ end
+
+ Page::Project::Menu.perform(&:go_to_operations_metrics)
+
+ Page::Project::Operations::Metrics::Show.perform do |dashboard|
+ expect(dashboard).to have_custom_metric('Throughput')
+ end
+ end
+
+ def verify_delete_custom_metric
+ Page::Project::Menu.perform(&:go_to_integrations_settings)
+ Page::Project::Settings::Integrations.perform(&:click_on_prometheus_integration)
+
+ Page::Project::Settings::Services::Prometheus.perform do |metrics_panel|
+ metrics_panel.click_on_custom_metric('Business / Throughput (req/sec)')
+ metrics_panel.delete_custom_metric
+ end
+
+ Page::Project::Menu.perform(&:go_to_operations_metrics)
+
+ Page::Project::Operations::Metrics::Show.perform do |dashboard|
+ expect(dashboard).not_to have_custom_metric('Throughput')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb
deleted file mode 100644
index f7463c69db1..00000000000
--- a/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Monitor' do
- describe 'Dashboards', :orchestrated, :kubernetes, quarantine: { type: :new } do
- before(:all) do
- @cluster = Service::KubernetesCluster.new.create!
- Flow::Login.sign_in
- create_project_to_monitor
- wait_for_deployment
- end
-
- before do
- Flow::Login.sign_in_unless_signed_in
- @project.visit!
- end
-
- after(:all) do
- @cluster&.remove!
- end
-
- 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
-
- expect(dashboard).to have_metrics
- expect(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
-
- expect(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
-
- dashboard.show_last('3 hours')
- expect(dashboard).to have_metrics
-
- dashboard.show_last('1 day')
- expect(dashboard).to have_metrics
- end
- end
-
- private
-
- def wait_for_deployment
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success_or_retry)
- Page::Project::Menu.perform(&:go_to_operations_metrics)
- end
-
- def create_project_to_monitor
- @project = Resource::Project.fabricate_via_api! do |project|
- project.name = 'cluster-with-prometheus'
- project.description = 'Cluster with Prometheus'
- end
-
- @cluster_props = Resource::KubernetesCluster.fabricate_via_browser_ui! do |cluster_settings|
- cluster_settings.project = @project
- cluster_settings.cluster = @cluster
- cluster_settings.install_helm_tiller = 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.directory = Pathname
- .new(__dir__)
- .join('../../../../../fixtures/monitored_auto_devops')
- push.commit_message = 'Create AutoDevOps compatible Project for Monitoring'
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/sanity/framework_spec.rb b/qa/qa/specs/features/sanity/framework_spec.rb
index aae0f0ade71..611c6c7b1ff 100644
--- a/qa/qa/specs/features/sanity/framework_spec.rb
+++ b/qa/qa/specs/features/sanity/framework_spec.rb
@@ -6,7 +6,7 @@ module QA
it 'succeeds' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- expect(page).to have_text('Open source software to collaborate on code')
+ expect(page).to have_text('A complete DevOps platform')
end
end
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
index 8b14184f3b7..dd3a50ac128 100644
--- a/qa/qa/specs/helpers/quarantine.rb
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -2,83 +2,87 @@
require 'rspec/core'
-module QA::Specs::Helpers
- module Quarantine
- include RSpec::Core::Pending
+module QA
+ module Specs
+ module Helpers
+ module Quarantine
+ include RSpec::Core::Pending
- extend self
+ extend self
- def configure_rspec
- RSpec.configure do |config|
- config.before(:context, :quarantine) do
- Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
- end
+ def configure_rspec
+ RSpec.configure do |config|
+ config.before(:context, :quarantine) do
+ Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
+ end
- config.before do |example|
- Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
+ config.before do |example|
+ Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
+ end
+ end
end
- end
- end
- # Skip the entire context if a context is quarantined. This avoids running
- # before blocks unnecessarily.
- def skip_or_run_quarantined_contexts(filters, example)
- return unless example.metadata.key?(:quarantine)
+ # Skip the entire context if a context is quarantined. This avoids running
+ # before blocks unnecessarily.
+ def skip_or_run_quarantined_contexts(filters, example)
+ return unless example.metadata.key?(:quarantine)
- skip_or_run_quarantined_tests_or_contexts(filters, example)
- end
+ skip_or_run_quarantined_tests_or_contexts(filters, example)
+ end
- # Skip tests in quarantine unless we explicitly focus on them.
- def skip_or_run_quarantined_tests_or_contexts(filters, example)
- if filters.key?(:quarantine)
- included_filters = filters_other_than_quarantine(filters)
+ # Skip tests in quarantine unless we explicitly focus on them.
+ def skip_or_run_quarantined_tests_or_contexts(filters, example)
+ if filters.key?(:quarantine)
+ included_filters = filters_other_than_quarantine(filters)
- # If :quarantine is focused, skip the test/context unless its metadata
- # includes quarantine and any other filters
- # E.g., Suppose a test is tagged :smoke and :quarantine, and another is tagged
- # :ldap and :quarantine. If we wanted to run just quarantined smoke tests
- # using `--tag quarantine --tag smoke`, without this check we'd end up
- # running that ldap test as well because of the :quarantine metadata.
- # We could use an exclusion filter, but this way the test report will list
- # the quarantined tests when they're not run so that we're aware of them
- skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
- else
- if example.metadata.key?(:quarantine)
- quarantine_message = %w(In quarantine)
- quarantine_tag = example.metadata[:quarantine]
+ # If :quarantine is focused, skip the test/context unless its metadata
+ # includes quarantine and any other filters
+ # E.g., Suppose a test is tagged :smoke and :quarantine, and another is tagged
+ # :ldap and :quarantine. If we wanted to run just quarantined smoke tests
+ # using `--tag quarantine --tag smoke`, without this check we'd end up
+ # running that ldap test as well because of the :quarantine metadata.
+ # We could use an exclusion filter, but this way the test report will list
+ # the quarantined tests when they're not run so that we're aware of them
+ skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
+ else
+ if example.metadata.key?(:quarantine)
+ quarantine_message = %w(In quarantine)
+ quarantine_tag = example.metadata[:quarantine]
- if !!quarantine_tag
- quarantine_message << case quarantine_tag
- when String
- ": #{quarantine_tag}"
- when Hash
- ": #{quarantine_tag[:issue]}"
- else
- ''
- end
- end
+ if !!quarantine_tag
+ quarantine_message << case quarantine_tag
+ when String
+ ": #{quarantine_tag}"
+ when Hash
+ ": #{quarantine_tag[:issue]}"
+ else
+ ''
+ end
+ end
- skip(quarantine_message.join(' ').strip)
+ skip(quarantine_message.join(' ').strip)
+ end
+ end
end
- end
- end
- def filters_other_than_quarantine(filter)
- filter.reject { |key, _| key == :quarantine }
- end
+ def filters_other_than_quarantine(filter)
+ filter.reject { |key, _| key == :quarantine }
+ end
- # Checks if a test or context should be skipped.
- #
- # Returns true if
- # - the metadata does not includes the :quarantine tag
- # or if
- # - the metadata includes the :quarantine tag
- # - and the filter includes other tags that aren't in the metadata
- def should_skip_when_focused?(metadata, included_filters)
- return true unless metadata.key?(:quarantine)
- return false if included_filters.empty?
+ # Checks if a test or context should be skipped.
+ #
+ # Returns true if
+ # - the metadata does not includes the :quarantine tag
+ # or if
+ # - the metadata includes the :quarantine tag
+ # - and the filter includes other tags that aren't in the metadata
+ def should_skip_when_focused?(metadata, included_filters)
+ return true unless metadata.key?(:quarantine)
+ return false if included_filters.empty?
- (metadata.keys & included_filters.keys).empty?
+ (metadata.keys & included_filters.keys).empty?
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index ac73cc00dbf..afeddeaa5d5 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -42,6 +42,8 @@ module QA
tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
+ tags_for_rspec.push(%w[--tag ~skip_live_env]) if QA::Runtime::Env.dot_com?
+
QA::Runtime::Env.supported_features.each_key do |key|
tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key
end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 90924ffd40e..f5e4d4e294b 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'rest-client'
+
module QA
module Support
module Api
@@ -7,6 +9,7 @@ module QA
HTTP_STATUS_CREATED = 201
HTTP_STATUS_NO_CONTENT = 204
HTTP_STATUS_ACCEPTED = 202
+ HTTP_STATUS_SERVER_ERROR = 500
def post(url, payload)
RestClient::Request.execute(
diff --git a/qa/qa/tools/delete_test_ssh_keys.rb b/qa/qa/tools/delete_test_ssh_keys.rb
new file mode 100644
index 00000000000..953e9fc63d1
--- /dev/null
+++ b/qa/qa/tools/delete_test_ssh_keys.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require_relative '../../qa'
+
+# This script deletes all selected test ssh keys for a specific user
+# Keys can be selected by a string matching part of the key's title and by created date
+# - Specify `title_portion` to delete only keys that include the string provided
+# - Specify `delete_before` to delete only keys that were created before the given date
+#
+# If `dry_run` is true the script will list the keys by title and indicate whether each will be deleted
+#
+# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
+# - GITLAB_QA_ACCESS_TOKEN should have API access and belong to the user whose keys will be deleted
+
+module QA
+ module Tools
+ class DeleteTestSSHKeys
+ include Support::Api
+
+ ITEMS_PER_PAGE = '100'
+
+ def initialize(title_portion: 'E2E test key:', delete_before: Date.today.to_s, dry_run: false)
+ raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
+
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
+ @title_portion = title_portion
+ @delete_before = Date.parse(delete_before)
+ @dry_run = dry_run
+ end
+
+ def run
+ STDOUT.puts 'Running...'
+
+ keys_head_response = head Runtime::API::Request.new(@api_client, "/user/keys", per_page: ITEMS_PER_PAGE).url
+ total_pages = keys_head_response.headers[:x_total_pages]
+
+ test_ssh_key_ids = fetch_test_ssh_key_ids(total_pages)
+ STDOUT.puts "Number of test ssh keys to be deleted: #{test_ssh_key_ids.length}"
+
+ return if dry_run?
+
+ delete_ssh_keys(test_ssh_key_ids) unless test_ssh_key_ids.empty?
+ STDOUT.puts "\nDone"
+ end
+
+ private
+
+ attr_reader :dry_run
+ alias_method :dry_run?, :dry_run
+
+ def delete_ssh_keys(ssh_key_ids)
+ STDOUT.puts "Deleting #{ssh_key_ids.length} ssh keys..."
+ ssh_key_ids.each do |key_id|
+ delete_response = delete Runtime::API::Request.new(@api_client, "/user/keys/#{key_id}").url
+ dot_or_f = delete_response.code == 204 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
+ print dot_or_f
+ end
+ end
+
+ def fetch_test_ssh_key_ids(pages)
+ key_ids = []
+
+ pages.to_i.times do |page_no|
+ get_keys_response = get Runtime::API::Request.new(@api_client, "/user/keys", page: (page_no + 1).to_s, per_page: ITEMS_PER_PAGE).url
+ keys = JSON.parse(get_keys_response.body).select do |key|
+ to_delete = key['title'].include?(@title_portion) && Date.parse(key['created_at']) < @delete_before
+
+ puts "Key title: #{key['title']}\tcreated_at: #{key['created_at']}\tdelete? #{to_delete}" if dry_run?
+
+ to_delete
+ end
+ key_ids.concat(keys.map { |key| key['id'] })
+ end
+
+ key_ids.uniq
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/jenkins/page/configure_job.rb b/qa/qa/vendor/jenkins/page/configure_job.rb
index 56a2602a003..471567ec828 100644
--- a/qa/qa/vendor/jenkins/page/configure_job.rb
+++ b/qa/qa/vendor/jenkins/page/configure_job.rb
@@ -9,16 +9,19 @@ module QA
class ConfigureJob < Page::Base
attr_accessor :job_name
- def initialize
- @path = "/job/#{@job_name}/configure"
+ def path
+ "/job/#{@job_name}/configure"
end
def configure(scm_url:)
set_git_source_code_management_url(scm_url)
click_build_when_change_is_pushed_to_gitlab
set_publish_status_to_gitlab
- click_save
- wait_for_configuration_to_save
+
+ Support::Retrier.retry_until(sleep_interval: 0.5) do
+ click_save
+ wait_for_configuration_to_save
+ end
end
private
@@ -58,8 +61,8 @@ module QA
end
def wait_for_configuration_to_save
- QA::Support::Waiter.wait_until(sleep_interval: 1.0) do
- !page.current_url.include?(@path)
+ QA::Support::Waiter.wait_until(max_duration: 10, raise_on_failure: false) do
+ !page.current_url.include?(path)
end
end
end
diff --git a/qa/qa/vendor/jenkins/page/last_job_console.rb b/qa/qa/vendor/jenkins/page/last_job_console.rb
index f41b91c2cdb..9fcbb8ab956 100644
--- a/qa/qa/vendor/jenkins/page/last_job_console.rb
+++ b/qa/qa/vendor/jenkins/page/last_job_console.rb
@@ -9,6 +9,8 @@ module QA
class LastJobConsole < Page::Base
attr_accessor :job_name
+ CONSOLE_OUTPUT_SELECTOR = '.console-output'
+
def path
"/job/#{@job_name}/lastBuild/console"
end
@@ -17,13 +19,23 @@ module QA
# Retry on errors such as:
# Selenium::WebDriver::Error::JavascriptError:
# javascript error: this.each is not a function
- Support::Retrier.retry_on_exception(reload_page: page) do
- page.has_text?('Finished: SUCCESS')
+ Support::Retrier.retry_on_exception(reload_page: page, sleep_interval: 1) do
+ has_console_output? && console_output.include?('Finished: SUCCESS')
end
end
def no_failed_status_update?
- page.has_no_text?('Failed to update Gitlab commit status')
+ !console_output.include?('Failed to update Gitlab commit status')
+ end
+
+ private
+
+ def has_console_output?
+ page.has_selector?(CONSOLE_OUTPUT_SELECTOR, wait: 1)
+ end
+
+ def console_output
+ page.find(CONSOLE_OUTPUT_SELECTOR).text
end
end
end