summaryrefslogtreecommitdiff
path: root/qa/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa')
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb1
-rw-r--r--qa/qa/fixtures/script_extensions/test.html6
-rw-r--r--qa/qa/flow/pipeline.rb21
-rw-r--r--qa/qa/page/admin/settings/component/performance_bar.rb2
-rw-r--r--qa/qa/page/admin/settings/component/snowplow.rb4
-rw-r--r--qa/qa/page/admin/settings/component/usage_statistics.rb2
-rw-r--r--qa/qa/page/base.rb10
-rw-r--r--qa/qa/page/component/access_tokens.rb4
-rw-r--r--qa/qa/page/component/confirm_modal.rb12
-rw-r--r--qa/qa/page/component/members_filter.rb9
-rw-r--r--qa/qa/page/component/wiki_page_form.rb1
-rw-r--r--qa/qa/page/dashboard/todos.rb6
-rw-r--r--qa/qa/page/main/login.rb15
-rw-r--r--qa/qa/page/main/menu.rb9
-rw-r--r--qa/qa/page/project/import/github.rb3
-rw-r--r--qa/qa/page/project/infrastructure/kubernetes/index.rb7
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/pipeline/index.rb50
-rw-r--r--qa/qa/page/project/pipeline/new.rb12
-rw-r--r--qa/qa/page/project/pipeline/show.rb40
-rw-r--r--qa/qa/page/project/pipeline_editor/show.rb22
-rw-r--r--qa/qa/page/project/settings/services/jenkins.rb16
-rw-r--r--qa/qa/resource/bulk_import_group.rb16
-rw-r--r--qa/qa/resource/project.rb40
-rw-r--r--qa/qa/resource/runner.rb41
-rw-r--r--qa/qa/runtime/api/repository_storage_moves.rb2
-rw-r--r--qa/qa/runtime/browser.rb23
-rw-r--r--qa/qa/runtime/env.rb8
-rw-r--r--qa/qa/runtime/script_extensions/interceptor.js158
-rw-r--r--qa/qa/scenario/bootable.rb2
-rw-r--r--qa/qa/scenario/shared_attributes.rb1
-rw-r--r--qa/qa/service/docker_run/base.rb6
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb6
-rw-r--r--qa/qa/service/praefect_manager.rb8
-rw-r--r--qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb4
-rw-r--r--qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb57
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb412
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb6
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb20
-rw-r--r--qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb38
-rw-r--r--qa/qa/specs/features/api/4_verify/remove_runner_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb2
-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/merge_request/revert/revert_commit_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb64
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb91
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb105
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb (renamed from qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb)20
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb (renamed from qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb)20
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb22
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb4
-rw-r--r--qa/qa/specs/helpers/feature_flag.rb34
-rw-r--r--qa/qa/specs/runner.rb19
-rw-r--r--qa/qa/support/api.rb13
-rw-r--r--qa/qa/support/formatters/feature_flag_formatter.rb36
-rw-r--r--qa/qa/support/formatters/test_stats_formatter.rb1
-rw-r--r--qa/qa/support/helpers/mask_token.rb19
-rw-r--r--qa/qa/support/helpers/plan.rb5
-rw-r--r--qa/qa/support/loglinking.rb4
-rw-r--r--qa/qa/support/matchers/have_matcher.rb1
-rw-r--r--qa/qa/support/page_error_checker.rb45
-rw-r--r--qa/qa/support/wait_for_requests.rb6
-rw-r--r--qa/qa/tools/reliable_report.rb125
-rw-r--r--qa/qa/tools/test_resources_handler.rb8
-rw-r--r--qa/qa/vendor/jenkins/page/configure_job.rb5
100 files changed, 1605 insertions, 304 deletions
diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb
index 8d94d03ef9b..5f2ff31b318 100644
--- a/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb
+++ b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb
@@ -16,6 +16,7 @@ deploy:
install:
stage: install
script:
+ - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc
- "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
- "npm install <%= package.name %>"
cache:
diff --git a/qa/qa/fixtures/script_extensions/test.html b/qa/qa/fixtures/script_extensions/test.html
new file mode 100644
index 00000000000..0be2c080fd8
--- /dev/null
+++ b/qa/qa/fixtures/script_extensions/test.html
@@ -0,0 +1,6 @@
+<html>
+ <head></head>
+ <body>
+ <h1>Hello world</h1>
+ </body>
+</html>
diff --git a/qa/qa/flow/pipeline.rb b/qa/qa/flow/pipeline.rb
index 999f352e834..d19b2530bb8 100644
--- a/qa/qa/flow/pipeline.rb
+++ b/qa/qa/flow/pipeline.rb
@@ -5,17 +5,24 @@ module QA
module Pipeline
module_function
- # In some cases we don't need to wait for anything, blocked, running or pending is acceptable
- # Some cases only we do need pipeline to finish with expected condition (completed, succeeded or replicated)
- def visit_latest_pipeline(pipeline_condition: nil)
+ # Acceptable statuses:
+ # canceled, created, failed, manual, passed
+ # pending, running, skipped
+ def visit_latest_pipeline(status: nil, wait: nil, skip_wait: true)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:"wait_for_latest_pipeline_#{pipeline_condition}") if pipeline_condition
- Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+ Page::Project::Pipeline::Index.perform do |index|
+ index.has_any_pipeline?(wait: wait)
+ index.wait_for_latest_pipeline(status: status, wait: wait) if status || !skip_wait
+ index.click_on_latest_pipeline
+ end
end
- def wait_for_latest_pipeline(pipeline_condition:)
+ def wait_for_latest_pipeline(status: nil, wait: nil)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:"wait_for_latest_pipeline_#{pipeline_condition}")
+ Page::Project::Pipeline::Index.perform do |index|
+ index.has_any_pipeline?(wait: wait)
+ index.wait_for_latest_pipeline(status: status, wait: wait)
+ end
end
end
end
diff --git a/qa/qa/page/admin/settings/component/performance_bar.rb b/qa/qa/page/admin/settings/component/performance_bar.rb
index 9e92fa362fb..ebf0e744b5e 100644
--- a/qa/qa/page/admin/settings/component/performance_bar.rb
+++ b/qa/qa/page/admin/settings/component/performance_bar.rb
@@ -12,7 +12,7 @@ module QA
end
def enable_performance_bar
- check_element(:enable_performance_bar_checkbox)
+ check_element(:enable_performance_bar_checkbox, true)
Capybara.current_session.driver.browser.manage.add_cookie(name: 'perf_bar_enabled', value: 'true')
end
diff --git a/qa/qa/page/admin/settings/component/snowplow.rb b/qa/qa/page/admin/settings/component/snowplow.rb
index e05679feac3..c7f103e29a8 100644
--- a/qa/qa/page/admin/settings/component/snowplow.rb
+++ b/qa/qa/page/admin/settings/component/snowplow.rb
@@ -31,11 +31,11 @@ module QA
private
def check_snowplow_enabled_checkbox
- check_element(:snowplow_enabled_checkbox)
+ check_element(:snowplow_enabled_checkbox, true)
end
def uncheck_snowplow_enabled_checkbox
- uncheck_element(:snowplow_enabled_checkbox)
+ uncheck_element(:snowplow_enabled_checkbox, true)
end
def click_save_changes_button
diff --git a/qa/qa/page/admin/settings/component/usage_statistics.rb b/qa/qa/page/admin/settings/component/usage_statistics.rb
index 0275b7ae926..c296e63e28e 100644
--- a/qa/qa/page/admin/settings/component/usage_statistics.rb
+++ b/qa/qa/page/admin/settings/component/usage_statistics.rb
@@ -11,7 +11,7 @@ module QA
end
def has_disabled_usage_data_checkbox?
- has_element?(:enable_usage_data_checkbox, disabled: true)
+ has_element?(:enable_usage_data_checkbox, disabled: true, visible: false)
end
end
end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 526dd25ccc9..83db8bc0fd6 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -133,7 +133,9 @@ module QA
end
def all_elements(name, **kwargs)
- if kwargs.keys.none? { |key| [:minimum, :maximum, :count, :between].include?(key) }
+ all_args = [:minimum, :maximum, :count, :between]
+
+ if kwargs.keys.none? { |key| all_args.include?(key) }
raise ArgumentError, "Please use :minimum, :maximum, :count, or :between so that all is more reliable"
end
@@ -247,6 +249,8 @@ module QA
else
find_element(name, **original_kwargs).disabled? == disabled
end
+ rescue Capybara::ElementNotFound
+ false
end
# Check for the element before waiting for requests, just in case unrelated requests are in progress.
@@ -467,8 +471,8 @@ module QA
return element_when_flag_disabled if has_element?(element_when_flag_disabled)
raise ElementNotFound,
- "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \
- "The relevant feature flag is #{feature_flag}"
+ "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \
+ "The relevant feature flag is #{feature_flag}"
end
end
end
diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb
index 3c8a6cf6a1d..6a9249621e1 100644
--- a/qa/qa/page/component/access_tokens.rb
+++ b/qa/qa/page/component/access_tokens.rb
@@ -19,7 +19,7 @@ module QA
end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
- element :api_checkbox, '#{scope}_checkbox' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
+ element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
@@ -36,7 +36,7 @@ module QA
end
def check_api
- check_element(:api_checkbox)
+ click_element(:api_label)
end
def click_create_token_button
diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb
index a90be76c879..76200490f66 100644
--- a/qa/qa/page/component/confirm_modal.rb
+++ b/qa/qa/page/component/confirm_modal.rb
@@ -8,10 +8,14 @@ module QA
def self.included(base)
super
+
+ base.view 'app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_modal.vue' do
+ element :confirm_ok_button
+ end
end
def fill_confirmation_text(text)
- fill_element :confirm_input, text
+ fill_element(:confirm_input, text)
end
def wait_for_confirm_button_enabled
@@ -22,7 +26,11 @@ module QA
def confirm_transfer
wait_for_confirm_button_enabled
- click_element :confirm_button
+ click_element(:confirm_button)
+ end
+
+ def click_confirmation_ok_button
+ click_element(:confirm_ok_button)
end
end
end
diff --git a/qa/qa/page/component/members_filter.rb b/qa/qa/page/component/members_filter.rb
index ac07fe7e9fa..fce4560d255 100644
--- a/qa/qa/page/component/members_filter.rb
+++ b/qa/qa/page/component/members_filter.rb
@@ -10,15 +10,14 @@ module QA
super
base.view 'app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue' do
- element :members_filtered_search_bar_content
+ element :search_bar_input
+ element :search_button
end
end
def search_member(username)
- # TODO: Update the two actions below to use direct qa selectors once this is implemented:
- # https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1688
- find_element(:members_filtered_search_bar_content).find('input').set(username)
- find('.gl-search-box-by-click-search-button').click
+ fill_element :search_bar_input, username
+ click_element :search_button
end
end
end
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index 8f504b784b2..74b6c6b2d5e 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -14,7 +14,6 @@ module QA
element :wiki_content_textarea
element :wiki_message_textbox
element :wiki_submit_button
- element :try_new_editor_container
element :editing_mode_button
end
diff --git a/qa/qa/page/dashboard/todos.rb b/qa/qa/page/dashboard/todos.rb
index d8baadcf73d..d5660823118 100644
--- a/qa/qa/page/dashboard/todos.rb
+++ b/qa/qa/page/dashboard/todos.rb
@@ -17,7 +17,11 @@ module QA
end
def has_todo_list?
- has_element? :todo_item_container
+ has_element?(:todo_item_container)
+ end
+
+ def has_no_todo_list?
+ has_no_element?(:todo_item_container)
end
def has_latest_todo_item_with_content?(action, title)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index c34b8f33a5d..d8b7bb90437 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -130,6 +130,10 @@ module QA
has_css?(".active", text: 'Standard')
end
+ def has_arkose_labs_token?
+ has_css?('[name="arkose_labs_token"][value]', visible: false)
+ end
+
def switch_to_sign_in_tab
click_element :sign_in_tab
end
@@ -174,6 +178,17 @@ module QA
fill_element :login_field, user.username
fill_element :password_field, user.password
+
+ if Runtime::Env.running_on_dot_com?
+ # Arkose only appears in staging.gitlab.com, gitlab.com, etc...
+
+ # Wait until the ArkoseLabs challenge has initialized
+ Support::WaitForRequests.wait_for_requests
+ Support::Waiter.wait_until(max_duration: 5, reload_page: false, raise_on_failure: false) do
+ has_arkose_labs_token?
+ end
+ end
+
click_element :sign_in_button
Support::WaitForRequests.wait_for_requests
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index e3bb585955b..0bb74455f81 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -45,6 +45,14 @@ module QA
element :search_term_field
end
+ view 'app/views/layouts/_header_search.html.haml' do
+ element :search_box
+ end
+
+ view 'app/assets/javascripts/header_search/components/app.vue' do
+ element :search_term_field
+ end
+
def go_to_groups
within_groups_menu do
click_element(:menu_item_link, title: 'Your groups')
@@ -146,6 +154,7 @@ module QA
end
def search_for(term)
+ click_element(:search_box)
fill_element :search_term_field, "#{term}\n"
end
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index 47f7e701ae8..89d044bac8d 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -75,11 +75,10 @@ module QA
# @return [Boolean]
def has_imported_project?(gh_project_name, wait: QA::Support::WaitForRequests::DEFAULT_MAX_WAIT_TIME)
within_element(:project_import_row, source_project: gh_project_name, skip_finished_loading_check: true) do
- # TODO: remove retrier with reload:true once https://gitlab.com/gitlab-org/gitlab/-/issues/292861 is fixed
wait_until(
max_duration: wait,
sleep_interval: 5,
- reload: true,
+ reload: false,
skip_finished_loading_check_on_refresh: true
) do
has_element?(:import_status_indicator, text: "Complete")
diff --git a/qa/qa/page/project/infrastructure/kubernetes/index.rb b/qa/qa/page/project/infrastructure/kubernetes/index.rb
index 0424682179e..34d2ad55429 100644
--- a/qa/qa/page/project/infrastructure/kubernetes/index.rb
+++ b/qa/qa/page/project/infrastructure/kubernetes/index.rb
@@ -6,12 +6,13 @@ module QA
module Infrastructure
module Kubernetes
class Index < Page::Base
- view 'app/assets/javascripts/clusters_list/components/clusters_view_all.vue' do
- element :connect_existing_cluster_button
+ view 'app/assets/javascripts/clusters_list/components/clusters_actions.vue' do
+ element :clusters_actions_button
end
def connect_existing_cluster
- click_link 'Connect existing cluster'
+ within_element(:clusters_actions_button) { click_button(class: 'dropdown-toggle-split') }
+ click_link 'Connect a cluster (certificate - deprecated)'
end
def has_cluster?(cluster)
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 340e40127c9..26fff85dd99 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -13,6 +13,7 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :initialize_with_readme_checkbox
+ element :initialize_with_sast_checkbox
element :project_name
element :project_path
element :project_description
@@ -20,10 +21,6 @@ module QA
element :visibility_radios
end
- view 'app/views/projects/_new_project_initialize_with_sast.html.haml' do
- element :initialize_with_sast_checkbox
- end
-
view 'app/views/projects/project_templates/_template.html.haml' do
element :use_template_button
element :template_option_row
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index d088ba76bc0..70ac35eeffe 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -21,54 +21,46 @@ module QA
element :run_pipeline_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_succeeded
- wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") }
+ view 'app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue' do
+ element :pipeline_row_container
end
- def wait_for_latest_pipeline_completed
- wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") || has_selector?(".ci-status-icon-failed") }
+ def latest_pipeline
+ all_elements(:pipeline_row_container, minimum: 1).first
end
- def wait_for_latest_pipeline_skipped
- wait_for_latest_pipeline_status { has_text?('skipped') }
+ def latest_pipeline_status
+ latest_pipeline.find(element_selector_css(:pipeline_commit_status)).text
end
- def wait_for_latest_pipeline_status
- wait_until(max_duration: 90, reload: true, sleep_interval: 5) { has_pipeline? }
+ # If no status provided, wait for pipeline to complete
+ def wait_for_latest_pipeline(status: nil, wait: nil, reload: false)
+ wait ||= Support::Repeater::DEFAULT_MAX_WAIT_TIME
+ finished_status = %w[passed failed canceled skipped manual]
- wait_until(reload: false, max_duration: 360) do
- within_element_by_index(:pipeline_commit_status, 0) { yield }
+ wait_until(max_duration: wait, reload: reload, sleep_interval: 1) do
+ status ? latest_pipeline_status == status : finished_status.include?(latest_pipeline_status)
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
-
- def has_pipeline?
- has_element? :pipeline_url_link
+ def has_any_pipeline?(wait: nil)
+ wait ||= Support::Repeater::DEFAULT_MAX_WAIT_TIME
+ wait_until(max_duration: wait) { has_element?(:pipeline_row_container) }
end
def has_no_pipeline?
- has_no_element? :pipeline_url_link
+ has_no_element?(:pipeline_row_container)
end
def click_run_pipeline_button
- click_element :run_pipeline_button, Page::Project::Pipeline::New
+ click_element(:run_pipeline_button, Page::Project::Pipeline::New)
+ end
+
+ def click_on_latest_pipeline
+ latest_pipeline.find(element_selector_css(:pipeline_url_link)).click
end
end
end
end
end
end
-
-QA::Page::Project::Pipeline::Index.prepend_mod_with('Page::Project::Pipeline::Index', namespace: QA)
diff --git a/qa/qa/page/project/pipeline/new.rb b/qa/qa/page/project/pipeline/new.rb
index 644a21b46e9..96a48e6240a 100644
--- a/qa/qa/page/project/pipeline/new.rb
+++ b/qa/qa/page/project/pipeline/new.rb
@@ -7,10 +7,20 @@ module QA
class New < QA::Page::Base
view 'app/assets/javascripts/pipeline_new/components/pipeline_new_form.vue' do
element :run_pipeline_button, required: true
+ element :ci_variable_row_container
+ element :ci_variable_key_field
+ element :ci_variable_value_field
end
def click_run_pipeline_button
- click_element :run_pipeline_button
+ click_element(:run_pipeline_button, Page::Project::Pipeline::Show)
+ end
+
+ def add_variable(key, value, row_index: 0)
+ within_element_by_index(:ci_variable_row_container, row_index) do
+ fill_element(:ci_variable_key_field, key)
+ fill_element(:ci_variable_value_field, value)
+ end
end
end
end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 6f4757a34e8..f499b748fb4 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -8,7 +8,7 @@ module QA
include Component::CiBadgeLink
view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
- element :pipeline_header
+ element :pipeline_header, required: true
end
view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
@@ -16,14 +16,14 @@ module QA
end
view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
- element :job_item_container
- element :job_link
+ element :job_item_container, required: true
+ element :job_link, required: true
element :job_action_button
end
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
- element :expand_pipeline_button
- element :child_pipeline
+ element :expand_linked_pipeline_button
+ element :linked_pipeline_container
end
view 'app/assets/javascripts/reports/components/report_section.vue' do
@@ -73,14 +73,18 @@ module QA
end
end
- def has_child_pipeline?(title: nil)
- title ? find_child_pipeline_by_title(title) : has_element?(:child_pipeline)
+ def has_linked_pipeline?(title: nil)
+ title ? find_linked_pipeline_by_title(title) : has_element?(:linked_pipeline_container)
end
- def has_no_child_pipeline?
- has_no_element?(:child_pipeline)
+ alias_method :has_child_pipeline?, :has_linked_pipeline?
+
+ def has_no_linked_pipeline?
+ has_no_element?(:linked_pipeline_container)
end
+ alias_method :has_no_child_pipeline?, :has_no_linked_pipeline?
+
def click_job(job_name)
# Retry due to transient bug https://gitlab.com/gitlab-org/gitlab/-/issues/347126
QA::Support::Retrier.retry_on_exception do
@@ -88,22 +92,24 @@ module QA
end
end
- def child_pipelines
- all_elements(:child_pipeline, minimum: 1)
+ def linked_pipelines
+ all_elements(:linked_pipeline_container, minimum: 1)
end
- def find_child_pipeline_by_title(title)
- child_pipelines.find { |pipeline| pipeline[:title].include?(title) }
+ def find_linked_pipeline_by_title(title)
+ linked_pipelines.find { |pipeline| pipeline[:title].include?(title) }
end
- def expand_child_pipeline(title: nil)
- child_pipeline = title ? find_child_pipeline_by_title(title) : child_pipelines.first
+ def expand_linked_pipeline(title: nil)
+ linked_pipeline = title ? find_linked_pipeline_by_title(title) : linked_pipelines.first
- within_element_by_index(:child_pipeline, child_pipelines.index(child_pipeline)) do
- click_element(:expand_pipeline_button)
+ within_element_by_index(:linked_pipeline_container, linked_pipelines.index(linked_pipeline)) do
+ click_element(:expand_linked_pipeline_button)
end
end
+ alias_method :expand_child_pipeline, :expand_linked_pipeline
+
def expand_license_report
within_element(:license_report_widget) do
click_element(:expand_report_button)
diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb
index caf54a10025..1a8e1e07994 100644
--- a/qa/qa/page/project/pipeline_editor/show.rb
+++ b/qa/qa/page/project/pipeline_editor/show.rb
@@ -15,9 +15,9 @@ module QA
element :target_branch_field, required: true
end
- view 'app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue' do
- element :toggle_sidebar_collapse_button
- element :drawer_content
+ view 'app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue' do
+ element :drawer_toggle, required: true
+ element :template_repo_link, required: true
end
view 'app/assets/javascripts/vue_shared/components/source_editor.vue' do
@@ -46,13 +46,6 @@ module QA
element :file_editor_container
end
- def initialize
- super
-
- wait_for_requests
- close_toggle_sidebar
- end
-
def open_branch_selector_dropdown
click_element(:branch_selector_button)
end
@@ -148,15 +141,6 @@ module QA
find('.nav-item', text: name).click
end
end
-
- # If the page thinks user has never opened pipeline editor before
- # It will expand pipeline editor sidebar by default
- # Collapse the sidebar if it is expanded
- def close_toggle_sidebar
- return unless has_element?(:drawer_content)
-
- click_element(:toggle_sidebar_collapse_button)
- end
end
end
end
diff --git a/qa/qa/page/project/settings/services/jenkins.rb b/qa/qa/page/project/settings/services/jenkins.rb
index 3d7da8d0161..8e092371491 100644
--- a/qa/qa/page/project/settings/services/jenkins.rb
+++ b/qa/qa/page/project/settings/services/jenkins.rb
@@ -7,10 +7,10 @@ module QA
module Services
class Jenkins < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
- element :jenkins_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
- element :project_name_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
- element :username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
- element :password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_jenkins_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_project_name_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do
@@ -28,19 +28,19 @@ module QA
private
def set_jenkins_url(jenkins_url)
- fill_element(:jenkins_url_field, jenkins_url)
+ fill_element(:service_jenkins_url_field, jenkins_url)
end
def set_project_name(project_name)
- fill_element(:project_name_field, project_name)
+ fill_element(:service_project_name_field, project_name)
end
def set_username(username)
- fill_element(:username_field, username)
+ fill_element(:service_username_field, username)
end
def set_password(password)
- fill_element(:password_field, password)
+ fill_element(:service_password_field, password)
end
def click_save_changes_button
diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb
index a22529152e1..31db8ae4cc6 100644
--- a/qa/qa/resource/bulk_import_group.rb
+++ b/qa/qa/resource/bulk_import_group.rb
@@ -7,10 +7,14 @@ module QA
:destination_group,
:import_id
- attribute :access_token do
+ attribute :import_access_token do
api_client.personal_access_token
end
+ attribute :gitlab_address do
+ QA::Runtime::Scenario.gitlab_address
+ end
+
# In most cases we will want to set path the same as source group
# but it can be set to a custom name as well when imported via API
attribute :destination_group_path do
@@ -19,18 +23,16 @@ module QA
# Can't define path as attribue since @path is set in base class initializer
alias_method :path, :destination_group_path
- delegate :gitlab_address, to: 'QA::Runtime::Scenario'
-
- def fabricate_via_browser_ui!
+ def fabricate!
Page::Main::Menu.perform(&:go_to_create_group)
Page::Group::New.perform do |group|
group.switch_to_import_tab
- group.connect_gitlab_instance(gitlab_address, api_client.personal_access_token)
+ group.connect_gitlab_instance(gitlab_address, import_access_token)
end
Page::Group::BulkImport.perform do |import_page|
- import_page.import_group(path, sandbox.path)
+ import_page.import_group(destination_group_path, sandbox.full_path)
end
reload!
@@ -49,7 +51,7 @@ module QA
{
configuration: {
url: gitlab_address,
- access_token: access_token
+ access_token: import_access_token
},
entities: [
{
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 740a8920cf2..dba3eb2e219 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -90,7 +90,12 @@ module QA
Page::Project::New.perform(&:click_blank_project_link)
Page::Project::New.perform do |new_page|
- new_page.choose_test_namespace unless @personal_namespace
+ if @personal_namespace
+ new_page.choose_namespace(@personal_namespace)
+ else
+ new_page.choose_test_namespace
+ end
+
new_page.choose_name(@name)
new_page.add_description(@description)
new_page.set_visibility(@visibility)
@@ -105,7 +110,16 @@ module QA
def fabricate_via_api!
resource_web_url(api_get)
rescue ResourceNotFoundError
- super
+ response = super
+
+ # If a project is being imported, wait until it completes before we let the test continue.
+ # Otherwise we see Git repository errors
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/356101
+ Support::Retrier.retry_until(max_duration: 60, sleep_interval: 5) do
+ %w[none finished].include?(reload!.api_resource[:import_status])
+ end
+
+ response
end
def api_get_path
@@ -295,13 +309,6 @@ module QA
merge_requests.find { |mr| mr[:title] == title }
end
- def runners(tag_list: nil)
- url = tag_list ? "#{api_runners_path}?tag_list=#{tag_list.compact.join(',')}" : api_runners_path
- response = get(request_url(url, per_page: '100'))
-
- parse_body(response)
- end
-
def registry_repositories
response = get(request_url(api_registry_repositories_path))
parse_body(response)
@@ -336,16 +343,17 @@ module QA
parse_body(response)
end
- def pipelines
- response = get(request_url(api_pipelines_path))
- parse_body(response)
- end
-
def pipeline_schedules
response = get(request_url(api_pipeline_schedules_path))
parse_body(response)
end
+ def pipelines(auto_paginate: false, attempts: 0)
+ return parse_body(api_get_from(api_pipelines_path)) unless auto_paginate
+
+ auto_paginated_response(request_url(api_pipelines_path, per_page: '100'), attempts: attempts)
+ end
+
def issues(auto_paginate: false, attempts: 0)
return parse_body(api_get_from(api_issues_path)) unless auto_paginate
@@ -380,9 +388,7 @@ module QA
api_resource[:import_status] == "finished"
end
- unless mirror_succeeded
- raise "Mirroring failed with error: #{api_resource[:import_error]}"
- end
+ raise "Mirroring failed with error: #{api_resource[:import_error]}" unless mirror_succeeded
end
def remove_via_api!
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 9c5c9992442..c014563671d 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -31,7 +31,7 @@ module QA
end
def fabricate_via_api!
- Service::DockerRun::GitlabRunner.new(name).tap do |runner|
+ @docker_container = Service::DockerRun::GitlabRunner.new(name).tap do |runner|
runner.pull
runner.token = @token ||= project.runners_token
runner.address = Runtime::Scenario.gitlab_address
@@ -46,12 +46,22 @@ module QA
end
def remove_via_api!
- runners = project.runners(tag_list: @tags)
+ runners = list_of_runners(tag_list: @tags)
- return if runners.blank?
+ # If we have no runners, print the logs from the runner docker container in case they show why it isn't running.
+ if runners.blank?
+ dump_logs
+
+ return
+ end
this_runner = runners.find { |runner| runner[:description] == name }
+
+ # As above, but now we should have a specific runner. If not, print the logs from the runner docker container
+ # to see if we can find out why the runner isn't running.
unless this_runner
+ dump_logs
+
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
@@ -62,6 +72,20 @@ module QA
Service::DockerRun::GitlabRunner.new(name).remove!
end
+ def list_of_runners(tag_list: nil)
+ url = tag_list ? "#{api_post_path}?tag_list=#{tag_list.compact.join(',')}" : api_post_path
+ response = get(request_url(url, per_page: '100'))
+
+ # Capturing 500 error code responses to log this issue better. We can consider cleaning it up once https://gitlab.com/gitlab-org/gitlab/-/issues/331753 is addressed.
+ raise "Response returned a #{response.code} error code. #{response.body}" if response.code == Support::API::HTTP_STATUS_SERVER_ERROR
+
+ parse_body(response)
+ end
+
+ def reload!
+ super if method(:running?).super_method.call
+ end
+
def api_delete_path
"/runners/#{id}"
end
@@ -70,10 +94,21 @@ module QA
end
def api_post_path
+ "/runners"
end
def api_post_body
end
+
+ private
+
+ def dump_logs
+ if @docker_container.running?
+ @docker_container.logs { |line| QA::Runtime::Logger.debug(line) }
+ else
+ QA::Runtime::Logger.debug("No runner container found named #{name}")
+ end
+ end
end
end
end
diff --git a/qa/qa/runtime/api/repository_storage_moves.rb b/qa/qa/runtime/api/repository_storage_moves.rb
index c3b2095be32..fb8d70c0836 100644
--- a/qa/qa/runtime/api/repository_storage_moves.rb
+++ b/qa/qa/runtime/api/repository_storage_moves.rb
@@ -24,7 +24,7 @@ module QA
Logger.debug('Getting repository storage moves')
Support::Waiter.wait_until do
- with_paginated_response_body(Request.new(api_client, "/#{resource_name(resource)}_repository_storage_moves", per_page: '100').url) do |page|
+ get(Request.new(api_client, "/#{resource_name(resource)}_repository_storage_moves", per_page: '100').url) do |page|
break true if page.any? { |item| yield item }
end
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index f1d93ce376a..89e84f414b1 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -194,6 +194,8 @@ module QA
def initialize(instance, page_class)
@session_address = Runtime::Address.new(instance, page_class)
@page_class = page_class
+
+ Session.enable_interception if Runtime::Env.can_intercept?
end
def url
@@ -255,6 +257,27 @@ module QA
@network_conditions_configured = false
end
+ def self.enable_interception
+ script = File.read("#{__dir__}/script_extensions/interceptor.js")
+ command = {
+ cmd: 'Page.addScriptToEvaluateOnNewDocument',
+ params: {
+ source: script
+ }
+ }
+ @interceptor_script_params = Capybara.current_session.driver.browser.send(:bridge).send_command(command)
+ end
+
+ def self.disable_interception
+ return unless @interceptor_script_params
+
+ command = {
+ cmd: 'Page.removeScriptToEvaluateOnNewDocument',
+ params: @interceptor_script_params
+ }
+ Capybara.current_session.driver.browser.send(:bridge).send_command(command)
+ end
+
private
def simulate_slow_connection
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 63207751c78..e4537009406 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -37,6 +37,14 @@ module QA
ENV['QA_PRAEFECT_REPOSITORY_STORAGE']
end
+ def interception_enabled?
+ enabled?(ENV['INTERCEPT_REQUESTS'], default: true)
+ end
+
+ def can_intercept?
+ browser == :chrome && interception_enabled?
+ end
+
def ci_job_url
ENV['CI_JOB_URL']
end
diff --git a/qa/qa/runtime/script_extensions/interceptor.js b/qa/qa/runtime/script_extensions/interceptor.js
new file mode 100644
index 00000000000..9e98b0421b4
--- /dev/null
+++ b/qa/qa/runtime/script_extensions/interceptor.js
@@ -0,0 +1,158 @@
+(() => {
+ const CACHE_NAME = 'INTERCEPTOR_CACHE';
+
+ /**
+ * Fetches and parses JSON from the sessionStorage cache
+ * @returns {(Object)}
+ */
+ const getCache = () => {
+ return JSON.parse(sessionStorage.getItem(CACHE_NAME));
+ };
+
+ /**
+ * Commits an object to the sessionStorage cache
+ * @param {Object} data
+ */
+ const saveCache = (data) => {
+ sessionStorage.setItem(CACHE_NAME, JSON.stringify(data));
+ };
+
+ /**
+ * Checks if the cache is available
+ * and if the current context has access to it
+ * @returns {boolean} can we access the cache?
+ */
+ const checkCache = () => {
+ try {
+ getCache();
+ return true;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.warn(`Couldn't access cache: ${error.toString()}`);
+ return false;
+ }
+ };
+
+ /**
+ * @callback cacheCommitCallback
+ * @param {object} cache
+ * @return {object} mutated cache
+ */
+
+ /**
+ * If the cache is available, takes a callback function that is called
+ * with an object returned from getCache,
+ * and saves whatever is returned from the callback function
+ * to the cache
+ * @param {cacheCommitCallback} cb
+ */
+ const commitToCache = (cb) => {
+ if (checkCache()) {
+ const cache = cb(getCache());
+ saveCache(cache);
+ }
+ };
+
+ window.Interceptor = {
+ saveCache,
+ commitToCache,
+ getCache,
+ checkCache,
+ activeFetchRequests: 0,
+ };
+
+ const pureFetch = window.fetch;
+ const pureXHROpen = window.XMLHttpRequest.prototype.open;
+
+ /**
+ * Replacement for XMLHttpRequest.prototype.open
+ * listens for complete xhr events
+ * if the xhr response has a status code higher than 400
+ * then commit request/response metadata to the cache
+ * @param method intercepted HTTP method (GET|POST|etc..)
+ * @param url intercepted HTTP url
+ * @param args intercepted XHR arguments (credentials, headers, options
+ * @return {Promise} the result of the original XMLHttpRequest.prototype.open implementation
+ */
+ function interceptXhr(method, url, ...args) {
+ this.addEventListener(
+ 'readystatechange',
+ () => {
+ const self = this;
+ if (this.readyState === XMLHttpRequest.DONE) {
+ if (this.status >= 400 || this.status === 0) {
+ commitToCache((cache) => {
+ // eslint-disable-next-line no-param-reassign
+ cache.errors ||= [];
+ cache.errors.push({
+ status: self.status === 0 ? -1 : self.status,
+ url,
+ method,
+ headers: { 'x-request-id': self.getResponseHeader('x-request-id') },
+ });
+ return cache;
+ });
+ }
+ }
+ },
+ false,
+ );
+ return pureXHROpen.apply(this, [method, url, ...args]);
+ }
+
+ /**
+ * Replacement for fetch implementation
+ * tracks active requests, and commits metadata to the cache
+ * if the response is not ok or was cancelled.
+ * Additionally tracks activeFetchRequests on the Interceptor object
+ * @param url target HTTP url
+ * @param opts fetch options, including request method, body, etc
+ * @param args additional fetch arguments
+ * @returns {Promise<"success"|"error">} the result of the original fetch call
+ */
+ async function interceptedFetch(url, opts, ...args) {
+ const method = opts && opts.method ? opts.method : 'GET';
+ window.Interceptor.activeFetchRequests += 1;
+ try {
+ const response = await pureFetch(url, opts, ...args);
+ window.Interceptor.activeFetchRequests += -1;
+ const clone = response.clone();
+
+ if (!clone.ok) {
+ commitToCache((cache) => {
+ // eslint-disable-next-line no-param-reassign
+ cache.errors ||= [];
+ cache.errors.push({
+ status: clone.status,
+ url,
+ method,
+ headers: { 'x-request-id': clone.headers.get('x-request-id') },
+ });
+ return cache;
+ });
+ }
+ return response;
+ } catch (error) {
+ commitToCache((cache) => {
+ // eslint-disable-next-line no-param-reassign
+ cache.errors ||= [];
+ cache.errors.push({
+ status: -1,
+ url,
+ method,
+ });
+ return cache;
+ });
+
+ window.Interceptor.activeFetchRequests += -1;
+ throw error;
+ }
+ }
+
+ if (checkCache()) {
+ saveCache({});
+ }
+
+ window.fetch = interceptedFetch;
+ window.XMLHttpRequest.prototype.open = interceptXhr;
+})();
diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb
index 2a9bbbc9fdb..8eedfab3cff 100644
--- a/qa/qa/scenario/bootable.rb
+++ b/qa/qa/scenario/bootable.rb
@@ -31,7 +31,7 @@ module QA
end
next
- elsif opt.name == :count_examples_only
+ elsif opt.name == :count_examples_only || opt.name == :test_metadata_only
parser.on(opt.arg, opt.desc) do |value|
QA::Runtime::Env.dry_run = true
Runtime::Scenario.define(opt.name, value)
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
index d5d7aedb47f..ad2d2d3a960 100644
--- a/qa/qa/scenario/shared_attributes.rb
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -14,6 +14,7 @@ module QA
attribute :parallel, '--parallel', 'Execute tests in parallel'
attribute :loop, '--loop', 'Execute test repeatedly'
attribute :count_examples_only, '--count-examples-only', 'Return the number of examples without running them'
+ attribute :test_metadata_only, '--test-metadata-only', 'Return all e2e test metadata without running them'
end
end
end
diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb
index 512960e8232..85c06e6c307 100644
--- a/qa/qa/service/docker_run/base.rb
+++ b/qa/qa/service/docker_run/base.rb
@@ -11,6 +11,12 @@ module QA
@runner_network = Runtime::Scenario.attributes[:runner_network] || @network
end
+ def logs
+ shell "docker logs #{@name}" do |line|
+ yield " #{line.chomp}"
+ end
+ end
+
def network
shell "docker network inspect #{@network}"
rescue CommandError
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 595d47bf162..0a8ac39dabd 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -43,6 +43,8 @@ module QA
#{@image} #{add_gitlab_tls_cert if @address.include? "https"} && docker exec --detach #{@name} sh -c "#{register_command}"
CMD
+ wait_until_running_and_configured
+
# Prove airgappedness
if runner_network == 'airgapped'
shell("docker exec #{@name} sh -c '#{prove_airgap}'")
@@ -111,6 +113,10 @@ module QA
&& docker cp #{gitlab_tls_certificate.path} #{@name}:/etc/gitlab-runner/certs/gitlab.test.crt
CMD
end
+
+ def wait_until_running_and_configured
+ wait_until_shell_command_matches("docker logs #{@name}", /Configuration loaded/)
+ end
end
end
end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index c364b00629c..1215268919c 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -530,9 +530,17 @@ module QA
storage_repositories[2..-3]
end
+ def modify_repo_access_time(node, repo_path, update_time)
+ repo = "/var/opt/gitlab/git-data/repositories/#{repo_path}"
+ shell(%{
+ docker exec --user git #{node} bash -c 'find #{repo} -exec touch -d "#{update_time}" {} \\;'
+ })
+ end
+
def add_repo_to_disk(node, repo_path)
cmd = "GIT_DIR=. git init --initial-branch=main /var/opt/gitlab/git-data/repositories/#{repo_path}"
shell "docker exec --user git #{node} bash -c '#{cmd}'"
+ modify_repo_access_time(node, repo_path, "24 hours ago")
end
def remove_repo_from_disk(repo_path)
diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
index c39db63f64d..79bba484bea 100644
--- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :github, :requires_admin, :reliable do
+ # Spec uses real github.com, which means outage of github.com can actually block deployment
+ # Keep spec in reliable bucket but don't run in blocking pipelines
+ RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin do
describe 'Project import', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353583' do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } }
diff --git a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
index 84eda023576..6585d08d2ac 100644
--- a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb
@@ -21,11 +21,11 @@ module QA
end
end
- let(:github_repo) { ENV['QA_LARGE_GH_IMPORT_REPO'] || 'rspec/rspec-core' }
- let(:import_max_duration) { ENV['QA_LARGE_GH_IMPORT_DURATION'] ? ENV['QA_LARGE_GH_IMPORT_DURATION'].to_i : 7200 }
+ let(:github_repo) { ENV['QA_LARGE_IMPORT_REPO'] || 'rspec/rspec-core' }
+ let(:import_max_duration) { ENV['QA_LARGE_IMPORT_DURATION'] ? ENV['QA_LARGE_IMPORT_DURATION'].to_i : 7200 }
let(:github_client) do
Octokit::Client.new(
- access_token: ENV['QA_LARGE_GH_IMPORT_GH_TOKEN'] || Runtime::Env.github_access_token,
+ access_token: ENV['QA_LARGE_IMPORT_GH_TOKEN'] || Runtime::Env.github_access_token,
auto_paginate: true
)
end
@@ -106,37 +106,47 @@ module QA
end
end
+ # rubocop:disable RSpec/InstanceVariable
after do |example|
user.remove_via_api! unless example.exception
next unless defined?(@import_time)
- # save data for comparison after run finished
+ # save data for comparison notification creation
save_json(
"data",
{
+ importer: :github,
import_time: @import_time,
reported_stats: @stats,
- github: {
+ source: {
+ name: "GitHub",
project_name: github_repo,
- branches: gh_branches.length,
- commits: gh_commits.length,
- labels: gh_labels.length,
- milestones: gh_milestones.length,
- prs: gh_prs.length,
- pr_comments: gh_prs.sum { |_k, v| v[:comments].length },
- issues: gh_issues.length,
- issue_comments: gh_issues.sum { |_k, v| v[:comments].length }
+ address: "https://github.com",
+ data: {
+ branches: gh_branches.length,
+ commits: gh_commits.length,
+ labels: gh_labels.length,
+ milestones: gh_milestones.length,
+ mrs: gh_prs.length,
+ mr_comments: gh_prs.sum { |_k, v| v[:comments].length },
+ issues: gh_issues.length,
+ issue_comments: gh_issues.sum { |_k, v| v[:comments].length }
+ }
},
- gitlab: {
+ target: {
+ name: "GitLab",
project_name: imported_project.path_with_namespace,
- branches: gl_branches.length,
- commits: gl_commits.length,
- labels: gl_labels.length,
- milestones: gl_milestones.length,
- mrs: mrs.length,
- mr_comments: mrs.sum { |_k, v| v[:comments].length },
- issues: gl_issues.length,
- issue_comments: gl_issues.sum { |_k, v| v[:comments].length }
+ address: QA::Runtime::Scenario.gitlab_address,
+ data: {
+ branches: gl_branches.length,
+ commits: gl_commits.length,
+ labels: gl_labels.length,
+ milestones: gl_milestones.length,
+ mrs: mrs.length,
+ mr_comments: mrs.sum { |_k, v| v[:comments].length },
+ issues: gl_issues.length,
+ issue_comments: gl_issues.sum { |_k, v| v[:comments].length }
+ }
},
not_imported: {
mrs: @mr_diff,
@@ -145,6 +155,7 @@ module QA
}
)
end
+ # rubocop:enable RSpec/InstanceVariable
it(
'imports large Github repo via api',
@@ -153,7 +164,7 @@ module QA
start = Time.now
# import the project and log gitlab path
- Runtime::Logger.info("== Importing project '#{github_repo}' in to '#{imported_project.reload!.full_path}' ==")
+ logger.info("== Importing project '#{github_repo}' in to '#{imported_project.reload!.full_path}' ==")
# fetch all objects right after import has started
fetch_github_objects
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
new file mode 100644
index 00000000000..edb7838e81d
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -0,0 +1,412 @@
+# frozen_string_literal: true
+
+# rubocop:disable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers
+module QA
+ RSpec.describe "Manage", requires_admin: 'uses admin API client for resource creation',
+ feature_flag: { name: 'bulk_import_projects', scope: :global },
+ only: { job: 'large-gitlab-import' } do
+ describe "Gitlab migration" do
+ let(:logger) { Runtime::Logger.logger }
+ let(:differ) { RSpec::Support::Differ.new(color: true) }
+ let(:gitlab_group) { ENV['QA_LARGE_IMPORT_GROUP'] || 'gitlab-migration' }
+ let(:gitlab_project) { ENV['QA_LARGE_IMPORT_REPO'] || 'dri' }
+ let(:gitlab_source_address) { 'https://staging.gitlab.com' }
+
+ let(:import_wait_duration) do
+ {
+ max_duration: (ENV['QA_LARGE_IMPORT_DURATION'] || 3600).to_i,
+ sleep_interval: 30
+ }
+ end
+
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+
+ # explicitly create PAT via api to not create it via UI in environments where admin token env var is not present
+ let(:target_api_client) do
+ Runtime::API::Client.new(
+ user: user,
+ personal_access_token: Resource::PersonalAccessToken.fabricate_via_api! do |pat|
+ pat.api_client = admin_api_client
+ end.token
+ )
+ end
+
+ let(:user) do
+ Resource::User.fabricate_via_api! do |usr|
+ usr.api_client = admin_api_client
+ end
+ end
+
+ let(:source_api_client) do
+ Runtime::API::Client.new(
+ gitlab_source_address,
+ personal_access_token: ENV["QA_LARGE_IMPORT_GL_TOKEN"],
+ is_new_session: false
+ )
+ end
+
+ let(:sandbox) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = admin_api_client
+ end
+ end
+
+ let(:destination_group) do
+ Resource::Group.fabricate_via_api! do |group|
+ group.api_client = admin_api_client
+ group.sandbox = sandbox
+ group.path = "imported-group-destination-#{SecureRandom.hex(4)}"
+ end
+ end
+
+ # Source group and it's objects
+ #
+ let(:source_group) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = source_api_client
+ group.path = gitlab_group
+ end
+ end
+
+ let(:source_project) { source_group.projects.find { |project| project.name.include?(gitlab_project) }.reload! }
+ let(:source_branches) { source_project.repository_branches(auto_paginate: true).map { |b| b[:name] } }
+ let(:source_commits) { source_project.commits(auto_paginate: true).map { |c| c[:id] } }
+ let(:source_labels) { source_project.labels(auto_paginate: true).map { |l| l.except(:id) } }
+ let(:source_milestones) { source_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
+ let(:source_pipelines) { source_project.pipelines.map { |pp| pp.except(:id, :web_url, :project_id) } }
+ let(:source_mrs) { fetch_mrs(source_project, source_api_client) }
+ let(:source_issues) { fetch_issues(source_project, source_api_client) }
+
+ # Imported group and it's objects
+ #
+ let(:imported_group) do
+ Resource::BulkImportGroup.fabricate_via_api! do |group|
+ group.import_access_token = source_api_client.personal_access_token # token for importing on source instance
+ group.api_client = target_api_client # token used by qa framework to access resources in destination instance
+ group.gitlab_address = gitlab_source_address
+ group.source_group = source_group
+ group.sandbox = destination_group
+ end
+ end
+
+ let(:imported_project) { imported_group.projects.find { |project| project.name.include?(gitlab_project) }.reload! }
+ let(:branches) { imported_project.repository_branches(auto_paginate: true).map { |b| b[:name] } }
+ let(:commits) { imported_project.commits(auto_paginate: true).map { |c| c[:id] } }
+ let(:labels) { imported_project.labels(auto_paginate: true).map { |l| l.except(:id) } }
+ let(:milestones) { imported_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
+ let(:pipelines) { imported_project.pipelines.map { |pp| pp.except(:id, :web_url, :project_id) } }
+ let(:mrs) { fetch_mrs(imported_project, target_api_client) }
+ let(:issues) { fetch_issues(imported_project, target_api_client) }
+
+ before do
+ Runtime::Feature.enable(:bulk_import_projects)
+
+ destination_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+ end
+
+ # rubocop:disable RSpec/InstanceVariable
+ after do |example|
+ next unless defined?(@import_time)
+
+ # save data for comparison notification creation
+ save_json(
+ "data",
+ {
+ importer: :gitlab,
+ import_time: @import_time,
+ source: {
+ name: "GitLab Source",
+ project_name: source_project.path_with_namespace,
+ address: gitlab_source_address,
+ data: {
+ branches: source_branches.length,
+ commits: source_commits.length,
+ labels: source_labels.length,
+ milestones: source_milestones.length,
+ pipelines: source_pipelines.length,
+ mrs: source_mrs.length,
+ mr_comments: source_mrs.sum { |_k, v| v[:comments].length },
+ issues: source_issues.length,
+ issue_comments: source_issues.sum { |_k, v| v[:comments].length }
+ }
+ },
+ target: {
+ name: "GitLab Target",
+ project_name: imported_project.path_with_namespace,
+ address: QA::Runtime::Scenario.gitlab_address,
+ data: {
+ branches: branches.length,
+ commits: commits.length,
+ labels: labels.length,
+ milestones: milestones.length,
+ pipelines: pipelines.length,
+ mrs: mrs.length,
+ mr_comments: mrs.sum { |_k, v| v[:comments].length },
+ issues: issues.length,
+ issue_comments: issues.sum { |_k, v| v[:comments].length }
+ }
+ },
+ not_imported: {
+ mrs: @mr_diff,
+ issues: @issue_diff
+ }
+ }
+ )
+ end
+ # rubocop:enable RSpec/InstanceVariable
+
+ it "migrates large gitlab group via api", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358842' do
+ start = Time.now
+
+ # trigger import and log imported group path
+ logger.info("== Importing group '#{gitlab_group}' in to '#{imported_group.full_path}' ==")
+
+ # fetch all objects right after import has started
+ fetch_source_gitlab_objects
+
+ # wait for import to finish and save import time
+ logger.info("== Waiting for import to be finished ==")
+ expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
+ @import_time = Time.now - start
+
+ aggregate_failures do
+ verify_repository_import
+ verify_labels_import
+ verify_milestones_import
+ verify_pipelines_import
+ verify_merge_requests_import
+ verify_issues_import
+ end
+ end
+
+ # Fetch source project objects for comparison
+ #
+ # @return [void]
+ def fetch_source_gitlab_objects
+ logger.info("== Fetching source group objects ==")
+
+ source_branches
+ source_commits
+ source_labels
+ source_milestones
+ source_pipelines
+ source_mrs
+ source_issues
+ end
+
+ # Verify repository imported correctly
+ #
+ # @return [void]
+ def verify_repository_import
+ logger.info("== Verifying repository import ==")
+ expect(imported_project.description).to eq(source_project.description)
+ expect(branches).to match_array(source_branches)
+ expect(commits).to match_array(source_commits)
+ end
+
+ # Verify imported labels
+ #
+ # @return [void]
+ def verify_labels_import
+ logger.info("== Verifying label import ==")
+ expect(labels).to include(*source_labels)
+ end
+
+ # Verify milestones import
+ #
+ # @return [void]
+ def verify_milestones_import
+ logger.info("== Verifying milestones import ==")
+ expect(milestones).to match_array(source_milestones)
+ end
+
+ # Verify pipelines import
+ #
+ # @return [void]
+ def verify_pipelines_import
+ logger.info("== Verifying pipelines import ==")
+ expect(pipelines).to match_array(source_pipelines)
+ end
+
+ # Verify imported merge requests and mr issues
+ #
+ # @return [void]
+ def verify_merge_requests_import
+ logger.info("== Verifying merge request import ==")
+ @mr_diff = verify_mrs_or_issues('mr')
+ end
+
+ # Verify imported issues and issue comments
+ #
+ # @return [void]
+ def verify_issues_import
+ logger.info("== Verifying issue import ==")
+ @issue_diff = verify_mrs_or_issues('issue')
+ end
+
+ # Verify imported mrs or issues and return missing items
+ #
+ # @param [String] type verification object, 'mr' or 'issue'
+ # @return [Hash]
+ def verify_mrs_or_issues(type)
+ # Compare length to have easy to read overview how many objects are missing
+ #
+ expected = type == 'mr' ? source_mrs : source_issues
+ actual = type == 'mr' ? mrs : issues
+ count_msg = "Expected to contain same amount of #{type}s. Source: #{expected.length}, Target: #{actual.length}"
+ expect(actual.length).to eq(expected.length), count_msg
+
+ missing_comments = verify_comments(type, actual, expected)
+
+ {
+ "#{type}s": (expected.keys - actual.keys).map { |it| actual[it].slice(:title, :url) },
+ "#{type}_comments": missing_comments
+ }
+ end
+
+ # Verify imported comments
+ #
+ # @param [String] type verification object, 'mrs' or 'issues'
+ # @param [Hash] actual
+ # @param [Hash] expected
+ # @return [Hash]
+ def verify_comments(type, actual, expected)
+ actual.each_with_object([]) do |(key, actual_item), missing_comments|
+ expected_item = expected[key]
+ title = actual_item[:title]
+ msg = "expected #{type} with title '#{title}' to have"
+
+ # Print title in the error message to see which object is missing
+ #
+ expect(actual_item).to be_truthy, "#{msg} been imported"
+ next unless expected_item
+
+ # Print difference in the description
+ #
+ expected_body = expected_item[:body]
+ actual_body = actual_item[:body]
+ body_msg = "#{msg} same description. diff:\n#{differ.diff(expected_body, actual_body)}"
+ expect(actual_body).to eq(expected_body), body_msg
+
+ # Print difference in state
+ #
+ expected_state = expected_item[:state]
+ actual_state = actual_item[:state]
+ state_msg = "#{msg} same state. Source: #{expected_state}, Target: #{actual_state}"
+ expect(actual_state).to eq(expected_state), state_msg
+
+ # Print amount difference first
+ #
+ expected_comments = expected_item[:comments]
+ actual_comments = actual_item[:comments]
+ comment_count_msg = <<~MSG
+ #{msg} same amount of comments. Source: #{expected_comments.length}, Target: #{actual_comments.length}
+ MSG
+ expect(actual_comments.length).to eq(expected_comments.length), comment_count_msg
+ expect(actual_comments).to match_array(expected_comments)
+
+ # Save missing comments
+ #
+ comment_diff = expected_comments - actual_comments
+ next if comment_diff.empty?
+
+ missing_comments << {
+ title: title,
+ target_url: actual_item[:url],
+ source_url: expected_item[:url],
+ missing_comments: comment_diff
+ }
+ end
+ end
+
+ private
+
+ # Project merge requests with comments
+ #
+ # @param [QA::Resource::Project]
+ # @param [Runtime::API::Client] client
+ # @return [Hash]
+ def fetch_mrs(project, client)
+ imported_mrs = project.merge_requests(auto_paginate: true, attempts: 2)
+
+ Parallel.map(imported_mrs, in_threads: 4) do |mr|
+ resource = Resource::MergeRequest.init do |resource|
+ resource.project = project
+ resource.iid = mr[:iid]
+ resource.api_client = client
+ end
+
+ [mr[:iid], {
+ url: mr[:web_url],
+ title: mr[:title],
+ body: sanitize_description(mr[:description]) || '',
+ state: mr[:state],
+ comments: resource
+ .comments(auto_paginate: true, attempts: 2)
+ .map { |c| sanitize_comment(c[:body]) }
+ }]
+ end.to_h
+ end
+
+ # Project issues with comments
+ #
+ # @param [QA::Resource::Project]
+ # @param [Runtime::API::Client] client
+ # @return [Hash]
+ def fetch_issues(project, client)
+ imported_issues = project.issues(auto_paginate: true, attempts: 2)
+
+ Parallel.map(imported_issues, in_threads: 4) do |issue|
+ resource = Resource::Issue.init do |issue_resource|
+ issue_resource.project = project
+ issue_resource.iid = issue[:iid]
+ issue_resource.api_client = client
+ end
+
+ [issue[:iid], {
+ url: issue[:web_url],
+ title: issue[:title],
+ state: issue[:state],
+ body: sanitize_description(issue[:description]) || '',
+ comments: resource
+ .comments(auto_paginate: true, attempts: 2)
+ .map { |c| sanitize_comment(c[:body]) }
+ }]
+ end.to_h
+ end
+
+ # Importer user mention pattern
+ #
+ # @return [Regex]
+ def created_by_pattern
+ @created_by_pattern ||= /\n\n \*By gitlab-migration on \S+ \(imported from GitLab\)\*/
+ end
+
+ # Remove added prefixes and legacy diff format from comments
+ #
+ # @param [String] body
+ # @return [String]
+ def sanitize_comment(body)
+ body&.gsub(created_by_pattern, "")
+ end
+
+ # Remove created by prefix from descripion
+ #
+ # @param [String] body
+ # @return [String]
+ def sanitize_description(body)
+ body&.gsub(created_by_pattern, "")
+ end
+
+ # Save json as file
+ #
+ # @param [String] name
+ # @param [Hash] json
+ # @return [void]
+ def save_json(name, json)
+ File.open("tmp/#{name}.json", "w") { |file| file.write(JSON.pretty_generate(json)) }
+ end
+ end
+ end
+end
+# rubocop:enable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
index 70f19e9f3d7..5a9cef6fded 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
@@ -3,7 +3,11 @@
module QA
# Disable on live envs until bulk_import_projects toggle is on by default
# Otherwise tests running in parallel can disable feature in the middle of other test
- RSpec.shared_context 'with gitlab project migration', :requires_admin, :skip_live_env do
+ RSpec.shared_context 'with gitlab project migration', requires_admin: 'creates a user via API',
+ feature_flag: {
+ name: 'bulk_import_projects',
+ scope: :global
+ } do
let(:source_project_with_readme) { false }
let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
let(:admin_api_client) { Runtime::API::Client.as_admin }
diff --git a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
index 624ddbb68e1..cd1b7730fa9 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/changing_repository_storage_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Changing Gitaly repository storage', :requires_admin do
+ describe 'Changing Gitaly repository storage', :requires_admin, except: { job: 'review-qa-*' } do
praefect_manager = Service::PraefectManager.new
shared_examples 'repository storage move' do
@@ -11,12 +11,16 @@ module QA
expect { project.change_repository_storage(destination_storage[:name]) }.not_to raise_error
expect { praefect_manager.verify_storage_move(source_storage, destination_storage, repo_type: :project) }.not_to raise_error
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
- push.file_name = 'new_file'
- push.file_content = '# This is a new file'
- push.commit_message = 'Add new file'
- push.new_branch = false
+ Support::Retrier.retry_on_exception(sleep_interval: 5) do
+ # For a short period of time after migrating, the repository can be 'read only' which may lead to errors
+ # 'The repository is temporarily read-only. Please try again later.'
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add new file'
+ commit.add_files([
+ { file_path: 'new_file', content: '# This is a new file' }
+ ])
+ end
end
expect(project).to have_file('README.md')
@@ -45,7 +49,7 @@ module QA
# Note: This test doesn't have the :orchestrated tag because it runs in the Test::Integration::Praefect
# scenario with other tests that aren't considered orchestrated.
# It also runs on staging using nfs-file07 as non-cluster storage and nfs-file22 as cluster/praefect storage
- context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284645', type: :investigating } do
+ context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347828' do
let(:source_storage) { { type: :gitaly, name: QA::Runtime::Env.non_cluster_repository_storage } }
let(:destination_storage) { { type: :praefect, name: QA::Runtime::Env.praefect_repository_storage } }
let(:project) do
diff --git a/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb b/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb
index 7a277d754c9..aae0329003b 100644
--- a/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb
+++ b/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb
@@ -70,7 +70,7 @@ module QA
end
end
- it 'sends an issues and note event', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349723' do
+ it 'sends an issues and note event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349723' do
setup_webhook(issues: true, note: true) do |webhook, smocker|
issue = Resource::Issue.fabricate_via_api! do |issue_init|
issue_init.project = webhook.project
diff --git a/qa/qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb b/qa/qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb
new file mode 100644
index 00000000000..c06912e0367
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'Create a new project from a template' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'templated-project'
+ project.template_name = 'dotnetcore'
+ end
+ end
+
+ it 'commits via the api', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/357234' do
+ expect do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.update_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: 'script'
+ }
+ ]
+ )
+ commit.add_files(
+ [
+ {
+ file_path: 'foo',
+ content: 'bar'
+ }
+ ]
+ )
+ end
+ end.not_to raise_exception
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
index 0d10783735b..6e6198328e5 100644
--- a/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
+++ b/qa/qa/specs/features/api/4_verify/remove_runner_spec.rb
@@ -7,7 +7,7 @@ module QA
let(:api_client) { Runtime::API::Client.new(:gitlab) }
let(:executor) { "qa-runner-#{Time.now.to_i}" }
- let(:runner_tags) { ['runner-registration-e2e-test'] }
+ let(:runner_tags) { ["runner-registration-e2e-test-#{Faker::Alphanumeric.alphanumeric(number: 8)}"] }
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.name = executor
@@ -15,21 +15,19 @@ module QA
end
end
- before do
- sleep 5 # Runner should register within 5 seconds
- end
-
# Removing a runner via the UI is covered by `spec/features/runners_spec.rb``
- it 'removes the runner', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/355302', type: :investigating } do
- expect(runner.project.runners.size).to eq(1)
- expect(runner.project.runners.first[:description]).to eq(executor)
+ it 'removes the runner', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/354828' do
+ runners = nil
+ expect { (runners = runner.list_of_runners(tag_list: runner_tags)).size }
+ .to eventually_eq(1).within(max_duration: 10, sleep_interval: 1)
+ expect(runners.first[:description]).to eq(executor)
- request = Runtime::API::Request.new(api_client, "runners/#{runner.project.runners.first[:id]}")
+ request = Runtime::API::Request.new(api_client, "runners/#{runners.first[:id]}")
response = delete(request.url)
expect(response.code).to eq(Support::API::HTTP_STATUS_NO_CONTENT)
expect(response.body).to be_empty
- expect(runner.project.runners).to be_empty
+ expect(runner.list_of_runners(tag_list: runner_tags)).to be_empty
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index 6bfb9c96fbd..80e660c1c1d 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -11,7 +11,7 @@ module QA
Page::Mattermost::Login.perform(&:sign_in_using_oauth)
Page::Mattermost::Main.perform do |mattermost|
- expect(mattermost).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ expect(mattermost).to have_content(/(GitLab Mattermost|What’s the name of your organization)/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
index 2933d580957..3921595204c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Create project badge' do
+ describe 'Create project badge', :reliable do
let(:badge_name) { "project-badge-#{SecureRandom.hex(8)}" }
let(:expected_badge_link_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}" }
let(:expected_badge_image_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}/badges/main/pipeline.svg" }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index d803f5e473c..3bf5a11b074 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :reliable, :github, :requires_admin do
+ # Spec uses real github.com, which means outage of github can actually block deployment
+ # Keep spec in reliable bucket but don't run in blocking pipelines
+ RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin do
describe 'Project import' do
let(:github_repo) { 'gitlab-qa-github/import-test' }
let(:api_client) { Runtime::API::Client.as_admin }
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
index 2aefa1c39ed..5d0befea1ce 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Personal project permissions' do
+ describe 'Personal project permissions', :reliable do
let!(:owner) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let!(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner) }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
index e7025920def..96f5731ea65 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan', :smoke, :reliable do
+ RSpec.describe 'Plan', :smoke do
describe 'mention' do
let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:project) do
diff --git a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
index e5a6c57656e..ea531d84634 100644
--- a/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/jenkins/jenkins_build_status_spec.rb
@@ -30,7 +30,7 @@ module QA
setup_project_integration_with_jenkins
- expect(page).to have_text("Jenkins CI activated.")
+ expect(page).to have_text("Jenkins settings saved and active.")
QA::Support::Retrier.retry_on_exception do
Resource::Repository::ProjectPush.fabricate! do |push|
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
index c4aacd8fb06..3373f4f4233 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
describe 'Merge request custom templates' do
let(:template_name) { 'custom_merge_request_template'}
let(:template_content) { 'This is a custom merge request template test' }
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 b0c6d01e8ca..d1e852979d0 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
@@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Merge request creation from fork', quarantine: {
- only: { subdomain: %i[canary production] },
+ only: :production,
issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/343801",
type: :investigation
} do
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb
index a2b27e294e6..8885163b5e3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
describe 'Reverting a commit' do
let(:file_name) { "secret_file.md" }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
index 90ca836f8b0..d66895de9c1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb
@@ -19,7 +19,7 @@ module QA
Flow::Login.sign_in
end
- it 'can be reverted', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347709', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/335987', type: :investigating } do
+ it 'can be reverted', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347709' do
revertable_merge_request.visit!
Page::MergeRequest::Show.perform do |merge_request|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb
index f335cfdb367..095444d99f1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
context 'File management' do
file_name = 'QA Test - File name'
file_content = 'QA Test - File content'
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
index 25c095d9eda..95e7a2a12d0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
context 'File management' do
let(:file) { Resource::File.fabricate_via_api! }
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
index ce99822b572..0560a5b125c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Multiple file snippet' do
+ describe 'Multiple file snippet', :reliable do
let(:snippet) do
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Personal snippet with multiple files'
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
index 70891ec72c7..77b3c4df7e1 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do
+ RSpec.describe 'Create', :reliable do
describe 'Multiple file snippet' do
let(:snippet) do
Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet|
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
index b6092ef0c4c..e9339342386 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Multiple file snippet' do
+ describe 'Multiple file snippet', :reliable do
let(:personal_snippet) do
Resource::Snippet.fabricate_via_api! do |snippet|
snippet.title = 'Personal snippet to delete file from'
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
index 6777c113f36..182a21a9377 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Sharing snippets' do
+ describe 'Sharing snippets', :reliable do
let(:snippet) do
Resource::Snippet.fabricate! do |snippet|
snippet.title = 'Shared snippet'
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
index 653c0657c81..e9871a70560 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
@@ -2,10 +2,13 @@
module QA
RSpec.describe 'Create' do
- describe 'Open a fork in Web IDE', quarantine: {
+ describe 'Open a fork in Web IDE',
+ # TODO: remove limitation to only run on main when the test is fixed
+ only: { pipeline: :main },
+ quarantine: {
issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/351696",
type: :flaky
- } do
+ } do
let(:parent_project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'parent-project'
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb
index 7704111ea21..5bb60e64da5 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb
@@ -135,7 +135,7 @@ module QA
def go_to_pipeline_job(user)
Flow::Login.sign_in(as: user)
project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('job')
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb
new file mode 100644
index 00000000000..496cc5f8a60
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module QA
+ # TODO:
+ # Remove FF :ci_trigger_forward_variables
+ # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed
+ RSpec.describe 'Verify', :runner, feature_flag: {
+ name: 'ci_trigger_forward_variables',
+ scope: :global
+ } do
+ describe 'UI defined variable' do
+ include_context 'variable inheritance test prep'
+
+ before do
+ add_ci_file(downstream1_project, [downstream1_ci_file])
+ add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file])
+
+ start_pipeline_with_variable
+ Page::Project::Pipeline::Show.perform do |show|
+ Support::Waiter.wait_until { show.passed? }
+ end
+ end
+
+ it(
+ 'is inheritable when forward:pipeline_variables is true',
+ :aggregate_failures,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358197'
+ ) do
+ visit_job_page('child1', 'child1_job')
+ verify_job_log_shows_variable_value
+
+ page.go_back
+
+ visit_job_page('downstream1', 'downstream1_job')
+ verify_job_log_shows_variable_value
+ end
+
+ def upstream_ci_file
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ stages:
+ - test
+ - deploy
+
+ child1_trigger:
+ stage: test
+ trigger:
+ include: .child1-ci.yml
+ forward:
+ pipeline_variables: true
+
+ downstream1_trigger:
+ stage: deploy
+ trigger:
+ project: #{downstream1_project.full_path}
+ forward:
+ pipeline_variables: true
+ YAML
+ }
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb
new file mode 100644
index 00000000000..2a0aaf6d7a3
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module QA
+ # TODO:
+ # Remove FF :ci_trigger_forward_variables
+ # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed
+ RSpec.describe 'Verify', :runner, feature_flag: {
+ name: 'ci_trigger_forward_variables',
+ scope: :global
+ } do
+ describe 'UI defined variable' do
+ include_context 'variable inheritance test prep'
+
+ before do
+ add_ci_file(downstream1_project, [downstream1_ci_file])
+ add_ci_file(downstream2_project, [downstream2_ci_file])
+ add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file, upstream_child2_ci_file])
+
+ start_pipeline_with_variable
+ Page::Project::Pipeline::Show.perform do |show|
+ Support::Waiter.wait_until { show.passed? }
+ end
+ end
+
+ it(
+ 'is not inheritable when forward:pipeline_variables is false',
+ :aggregate_failures,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358199'
+ ) do
+ visit_job_page('child1', 'child1_job')
+ verify_job_log_does_not_show_variable_value
+
+ page.go_back
+
+ visit_job_page('downstream1', 'downstream1_job')
+ verify_job_log_does_not_show_variable_value
+ end
+
+ it(
+ 'is not inheritable by default',
+ :aggregate_failures,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358200'
+ ) do
+ visit_job_page('child2', 'child2_job')
+ verify_job_log_does_not_show_variable_value
+
+ page.go_back
+
+ visit_job_page('downstream2', 'downstream2_job')
+ verify_job_log_does_not_show_variable_value
+ end
+
+ def upstream_ci_file
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ stages:
+ - test
+ - deploy
+
+ child1_trigger:
+ stage: test
+ trigger:
+ include: .child1-ci.yml
+ forward:
+ pipeline_variables: false
+
+ # default behavior
+ child2_trigger:
+ stage: test
+ trigger:
+ include: .child2-ci.yml
+
+ downstream1_trigger:
+ stage: deploy
+ trigger:
+ project: #{downstream1_project.full_path}
+ forward:
+ pipeline_variables: false
+
+ # default behavior
+ downstream2_trigger:
+ stage: deploy
+ trigger:
+ project: #{downstream2_project.full_path}
+ YAML
+ }
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index bd3135bafdc..1bba5355790 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -1,7 +1,10 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :smoke, :runner do
+ RSpec.describe 'Verify', :smoke, :runner, quarantine: {
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/356295',
+ type: :investigating
+ } do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
@@ -58,6 +61,16 @@ module QA
artifacts:
paths:
- my-artifacts/
+
+ test-coverage-report:
+ tags:
+ - #{executor}
+ script: mkdir coverage; echo "CONTENTS" > coverage/cobertura.xml
+ artifacts:
+ reports:
+ coverage_report:
+ coverage_format: cobertura
+ path: coverage/cobertura.xml
YAML
}
]
@@ -71,7 +84,8 @@ module QA
'test-success': 'passed',
'test-failure': 'failed',
'test-tags-mismatch': 'pending',
- 'test-artifacts': 'passed'
+ 'test-artifacts': 'passed',
+ 'test-coverage-report': 'passed'
}.each do |job, status|
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job(job)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb
index 9521cd20fc5..2fa6b9179ef 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb
@@ -32,7 +32,7 @@ module QA
add_included_files
add_main_ci_file
project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'succeeded')
+ Flow::Pipeline.visit_latest_pipeline(status: 'passed')
end
after do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb
index 9abb25c8dc1..3356d1274c8 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/locked_artifacts_spec.rb
@@ -56,7 +56,7 @@ module QA
)
end.project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job('test-artifacts')
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb
new file mode 100644
index 00000000000..0d8756fc9a3
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Verify' do
+ describe 'Multi-project pipelines' do
+ let(:downstream_job_name) { 'downstream_job' }
+ let(:executor) { "qa-runner-#{SecureRandom.hex(4)}" }
+ let!(:group) { Resource::Group.fabricate_via_api! }
+
+ let(:upstream_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = group
+ project.name = 'upstream-project'
+ end
+ end
+
+ let(:downstream_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = group
+ project.name = 'downstream-project'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate_via_api! do |runner|
+ runner.token = group.reload!.runners_token
+ runner.name = executor
+ runner.tags = [executor]
+ end
+ end
+
+ before do
+ add_ci_file(downstream_project, downstream_ci_file)
+ add_ci_file(upstream_project, upstream_ci_file)
+
+ Flow::Login.sign_in
+ upstream_project.visit!
+ Flow::Pipeline.visit_latest_pipeline(status: 'passed')
+ end
+
+ after do
+ runner.remove_via_api!
+ [upstream_project, downstream_project].each(&:remove_via_api!)
+ end
+
+ it(
+ 'creates a multi-project pipeline',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358064'
+ ) do
+ Page::Project::Pipeline::Show.perform do |show|
+ expect(show).to have_passed
+ expect(show).not_to have_job(downstream_job_name)
+
+ show.expand_linked_pipeline
+
+ expect(show).to have_job(downstream_job_name)
+ end
+ end
+
+ private
+
+ def add_ci_file(project, file)
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add CI config file'
+ commit.add_files([file])
+ end
+ end
+
+ def upstream_ci_file
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ stages:
+ - test
+ - deploy
+
+ job1:
+ stage: test
+ tags: ["#{executor}"]
+ script: echo 'done'
+
+ staging:
+ stage: deploy
+ trigger:
+ project: #{downstream_project.path_with_namespace}
+ strategy: depend
+ YAML
+ }
+ end
+
+ def downstream_ci_file
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ "#{downstream_job_name}":
+ stage: test
+ tags: ["#{executor}"]
+ script: echo 'done'
+ YAML
+ }
+ 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/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
index e34f41b4c95..5b7a569fa8a 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/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :runner, :reliable do
+ RSpec.describe 'Verify', :runner, :reliable do
describe 'Parent-child pipelines dependent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -25,23 +25,29 @@ module QA
runner.remove_via_api!
end
- it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348092' do
+ it(
+ 'parent pipelines passes if child passes',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358062'
+ ) do
add_ci_files(success_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
- expect(parent_pipeline).to have_passed
+ expect { parent_pipeline.has_passed? }.to eventually_be_truthy
end
end
- it 'parent pipeline fails if child fails', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348091' do
+ it(
+ 'parent pipeline fails if child fails',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358063'
+ ) do
add_ci_files(fail_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
- expect(parent_pipeline).to have_failed
+ expect { parent_pipeline.has_failed? }.to eventually_be_truthy
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb
index ef0c8d35c37..9e3c29db9e7 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/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Release', :runner, :reliable do
+ RSpec.describe 'Verify', :runner, :reliable do
describe 'Parent-child pipelines independent relationship' do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -25,23 +25,29 @@ module QA
runner.remove_via_api!
end
- it 'parent pipelines passes if child passes', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348093' do
+ it(
+ 'parent pipelines passes if child passes',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358059'
+ ) do
add_ci_files(success_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
- expect(parent_pipeline).to have_passed
+ expect { parent_pipeline.has_passed? }.to eventually_be_truthy
end
end
- it 'parent pipeline passes even if child fails', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348094' do
+ it(
+ 'parent pipeline passes even if child fails',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358060'
+ ) do
add_ci_files(fail_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'completed')
+ Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |parent_pipeline|
expect(parent_pipeline).to have_child_pipeline
- expect(parent_pipeline).to have_passed
+ expect { parent_pipeline.has_passed? }.to eventually_be_truthy
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
index 0bc3fb7b829..bbcc71bade7 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
@@ -3,8 +3,8 @@
module QA
RSpec.describe 'Verify', :runner do
describe 'Pass dotenv variables to downstream via bridge' do
- let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(8)}" }
- let(:upstream_var) { Faker::Alphanumeric.alphanumeric(8) }
+ let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+ let(:upstream_var) { Faker::Alphanumeric.alphanumeric(number: 8) }
let(:group) { Resource::Group.fabricate_via_api! }
let(:upstream_project) do
@@ -34,7 +34,7 @@ module QA
add_ci_file(downstream_project, downstream_ci_file)
add_ci_file(upstream_project, upstream_ci_file)
upstream_project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'succeeded')
+ Flow::Pipeline.visit_latest_pipeline(status: 'passed')
end
after do
@@ -44,8 +44,8 @@ module QA
it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348088' do
Page::Project::Pipeline::Show.perform do |parent_pipeline|
- Support::Waiter.wait_until { parent_pipeline.has_child_pipeline? }
- parent_pipeline.expand_child_pipeline
+ Support::Waiter.wait_until { parent_pipeline.has_linked_pipeline? }
+ parent_pipeline.expand_linked_pipeline
parent_pipeline.click_job('downstream_test')
end
@@ -73,7 +73,7 @@ module QA
stage: build
tags: ["#{executor}"]
script:
- - echo "DYNAMIC_ENVIRONMENT_VAR=#{upstream_var}" >> variables.env
+ - for i in `seq 1 20`; do echo "VAR_$i=#{upstream_var}" >> variables.env; done;
artifacts:
reports:
dotenv: variables.env
@@ -81,7 +81,7 @@ module QA
trigger:
stage: deploy
variables:
- PASSED_MY_VAR: $DYNAMIC_ENVIRONMENT_VAR
+ PASSED_MY_VAR: "$VAR_#{rand(1..20)}"
trigger: #{downstream_project.full_path}
YAML
}
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb
index 0e7a38626aa..b9f616aa733 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_can_create_merge_request_spec.rb
@@ -22,7 +22,7 @@ module QA
it(
'can create merge request',
- test_case: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349130'
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349130'
) do
Page::Project::PipelineEditor::New.perform(&:create_new_ci)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
index d1e9981ae74..f36593218a9 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb
@@ -42,7 +42,7 @@ module QA
it 'can trigger pipeline', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348011' do
Page::Project::Pipeline::Index.perform do |index|
- expect(index).not_to have_pipeline # should not auto trigger pipeline
+ expect(index).to have_no_pipeline # should not auto trigger pipeline
index.click_run_pipeline_button
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb
index 7a2c2b4ae90..fb7e3a8437f 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb
@@ -1,11 +1,10 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :runner, quarantine: {
- type: :flaky,
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351994'
- } do
+ RSpec.describe 'Verify', :runner do
describe 'Run pipeline with manual jobs' do
+ let(:executor) { "qa-runner-#{SecureRandom.hex(4)}" }
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pipeline-with-manual-job'
@@ -16,7 +15,8 @@ module QA
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.project = project
- runner.name = "qa-runner-#{SecureRandom.hex(3)}"
+ runner.name = executor
+ runner.tags = [executor]
end
end
@@ -36,22 +36,26 @@ module QA
Prep:
stage: Stage1
+ tags: ["#{executor}"]
script: exit 0
when: manual
Build:
stage: Stage2
+ tags: ["#{executor}"]
needs: ['Prep']
script: exit 0
parallel: 6
Test:
stage: Stage3
+ tags: ["#{executor}"]
needs: ['Build']
script: exit 0
Deploy:
stage: Stage3
+ tags: ["#{executor}"]
needs: ['Test']
script: exit 0
parallel: 6
@@ -65,15 +69,17 @@ module QA
before do
Flow::Login.sign_in
project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'skipped')
+ Flow::Pipeline.visit_latest_pipeline(status: 'skipped')
end
after do
runner&.remove_via_api!
- project&.remove_via_api!
end
- it 'does not leave any job in skipped state', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349158' do
+ it(
+ 'does not leave any job in skipped state',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349158'
+ ) do
Page::Project::Pipeline::Show.perform do |show|
show.click_job_action('Prep') # Trigger pipeline manually
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb
index ed46481d3be..1c75beebb48 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb
@@ -23,7 +23,7 @@ module QA
Flow::Login.sign_in
add_ci_files
project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'succeeded')
+ Flow::Pipeline.visit_latest_pipeline(status: 'passed')
end
after do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
index 94ac857f0fe..205b4d1168a 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
@@ -23,7 +23,7 @@ module QA
Flow::Login.sign_in
add_ci_files
project.visit!
- Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'succeeded')
+ Flow::Pipeline.visit_latest_pipeline(status: 'passed')
end
after do
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 8aa01888ae3..f8261bba342 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
@@ -22,8 +22,6 @@ module QA
Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Page::Project::Settings::CiCd.perform do |settings|
- sleep 5 # Runner should register within 5 seconds
-
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
expect(page).to have_online_runner
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
index c833aa1a5b8..f570ad335fe 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
@@ -3,6 +3,8 @@
module QA
RSpec.describe 'Package', :orchestrated, :skip_live_env do
describe 'Self-managed Container Registry' do
+ include Support::Helpers::MaskToken
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-registry'
@@ -110,9 +112,9 @@ module QA
let(:auth_token) do
case authentication_token_type
when :personal_access_token
- "\"#{personal_access_token}\""
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project)
when :project_deploy_token
- "\"#{project_deploy_token.token}\""
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project)
when :ci_job_token
'$CI_JOB_TOKEN'
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
index 2da0f6a0cf8..d5ef9dce10d 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
@@ -8,6 +8,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'composer-package-project'
+ project.visibility = :private
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb
index 22495796605..1840ae4e7f8 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb
@@ -12,6 +12,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'conan-package-project'
+ project.visibility = :private
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb
index 71acc3a8f92..e37102c17f7 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb
@@ -8,6 +8,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'generic-package-project'
+ project.visibility = :private
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb
index d2e816f9bf9..078465770c6 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb
@@ -5,6 +5,7 @@ module QA
describe 'Helm Registry' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+ include Support::Helpers::MaskToken
include_context 'packages registry qa scenario'
let(:package_name) { "gitlab_qa_helm-#{SecureRandom.hex(8)}" }
@@ -32,11 +33,13 @@ module QA
let(:access_token) do
case authentication_token_type
when :personal_access_token
- personal_access_token
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: package_project)
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: client_project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :project_deploy_token
- project_deploy_token.token
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: package_project)
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: client_project)
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
index 04aaefbaf5c..0ee5f1b6a0b 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
@@ -5,6 +5,7 @@ module QA
describe 'npm instance level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+ include Support::Helpers::MaskToken
let!(:registry_scope) { Runtime::Namespace.sandbox_name }
let!(:personal_access_token) do
@@ -78,11 +79,13 @@ module QA
let(:auth_token) do
case authentication_token_type
when :personal_access_token
- "\"#{personal_access_token}\""
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project)
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: another_project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :project_deploy_token
- "\"#{project_deploy_token.token}\""
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project)
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: another_project)
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
index cad1802f3e9..5ebcb94d0d0 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
@@ -5,6 +5,7 @@ module QA
describe 'npm project level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+ include Support::Helpers::MaskToken
let!(:registry_scope) { Runtime::Namespace.sandbox_name }
let!(:personal_access_token) do
@@ -34,6 +35,7 @@ module QA
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'npm-project-level'
+ project.visibility = :private
end
end
@@ -69,11 +71,11 @@ module QA
let(:auth_token) do
case authentication_token_type
when :personal_access_token
- "\"#{personal_access_token}\""
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token, project: project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :project_deploy_token
- "\"#{project_deploy_token.token}\""
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project)
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb
index b0a6555a16b..0ddb59d6625 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_group_level_spec.rb
@@ -5,6 +5,7 @@ module QA
describe 'NuGet group level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+ include Support::Helpers::MaskToken
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -61,6 +62,8 @@ module QA
after do
runner.remove_via_api!
package.remove_via_api!
+ project.remove_via_api!
+ another_project.remove_via_api!
end
where(:case_name, :authentication_token_type, :token_name, :testcase) do
@@ -73,11 +76,13 @@ module QA
let(:auth_token_password) do
case authentication_token_type
when :personal_access_token
- "\"#{personal_access_token.token}\""
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token.token, project: project)
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token.token, project: another_project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :group_deploy_token
- "\"#{group_deploy_token.token}\""
+ use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: project)
+ use_ci_variable(name: 'GROUP_DEPLOY_TOKEN', value: group_deploy_token.token, project: another_project)
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
index 4cac055634e..d5fd78480d2 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
@@ -3,6 +3,8 @@
module QA
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'NuGet project level endpoint' do
+ include Support::Helpers::MaskToken
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'nuget-package-project'
@@ -77,11 +79,11 @@ module QA
let(:auth_token_password) do
case authentication_token_type
when :personal_access_token
- "\"#{personal_access_token.token}\""
+ use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: personal_access_token.token, project: project)
when :ci_job_token
'${CI_JOB_TOKEN}'
when :project_deploy_token
- "\"#{project_deploy_token.token}\""
+ use_ci_variable(name: 'PROJECT_DEPLOY_TOKEN', value: project_deploy_token.token, project: project)
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
index a0c2eca5bd2..4614eced300 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
@@ -4,10 +4,12 @@ module QA
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'PyPI Repository' do
include Runtime::Fixtures
+ include Support::Helpers::MaskToken
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pypi-package-project'
+ project.visibility = :private
end
end
@@ -30,7 +32,7 @@ module QA
let(:uri) { URI.parse(Runtime::Scenario.gitlab_address) }
let(:gitlab_address_with_port) { "#{uri.scheme}://#{uri.host}:#{uri.port}" }
let(:gitlab_host_with_port) { "#{uri.host}:#{uri.port}" }
- let(:personal_access_token) { Runtime::Env.personal_access_token }
+ let(:personal_access_token) { use_ci_variable(name: 'PERSONAL_ACCESS_TOKEN', value: Runtime::Env.personal_access_token, project: project) }
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
index b2208dc644c..409a1c10943 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage do
+ RSpec.describe 'Package', :orchestrated, :packages, :object_storage,
+ feature_flag: { name: 'rubygem_packages', scope: :project } do
describe 'RubyGems Repository' do
include Runtime::Fixtures
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'rubygems-package-project'
+ project.visibility = :private
end
end
diff --git a/qa/qa/specs/helpers/feature_flag.rb b/qa/qa/specs/helpers/feature_flag.rb
new file mode 100644
index 00000000000..b9de2332c19
--- /dev/null
+++ b/qa/qa/specs/helpers/feature_flag.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+
+module QA
+ module Specs
+ module Helpers
+ module FeatureFlag
+ extend self
+
+ def skip_or_run_feature_flag_tests_or_contexts(example)
+ if example.metadata.key?(:feature_flag)
+ feature_flag_tag = example.metadata[:feature_flag]
+
+ global_feature_flag_message = 'Skipping on .com environments due to global feature flag usage'
+ feature_flag_message = 'Skipping on production due to feature flag usage'
+
+ if feature_flag_tag.is_a?(Hash) && feature_flag_tag[:scope] == :global
+ # Tests using a global feature flag will be skipped on live .com environments.
+ # This is to avoid flakiness with other tests running in parallel on the same environment
+ # as well as interfering with feature flag experimentation done by development groups.
+ example.metadata[:skip] = global_feature_flag_message if ContextSelector.dot_com?
+ else
+ # Tests using a feature flag scoped to an actor (ex: :project, :user, :group), or
+ # with no scope defined (such as in the case of a low risk global feature flag),
+ # will only be skipped in canary and production due to no admin account existing there.
+ example.metadata[:skip] = feature_flag_message if ContextSelector.context_matches?(:production)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 2b9adf0e870..c30e5d822c4 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -52,6 +52,8 @@ module QA
tags_for_rspec
end
+ # rubocop:disable Metrics/AbcSize
+ # rubocop:disable Metrics/CyclomaticComplexity
def perform
args = []
args.push('--tty') if tty
@@ -89,12 +91,29 @@ module QA
File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
$stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''}"
+ elsif Runtime::Scenario.attributes[:test_metadata_only]
+ args.unshift('--dry-run')
+
+ output_file = Pathname.new(File.join(Runtime::Path.qa_root, 'tmp', 'test-metadata.json'))
+
+ RSpec.configure do |config|
+ config.add_formatter(QA::Support::JsonFormatter, output_file)
+ config.fail_if_no_examples = true
+ end
+
+ RSpec::Core::Runner.run(args.flatten, $stderr, $stdout) do |status|
+ abort if status.nonzero?
+ end
+
+ $stdout.puts "Saved to file: #{output_file}"
else
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status|
abort if status.nonzero?
end
end
end
+ # rubocop:enable Metrics/AbcSize
+ # rubocop:enable Metrics/CyclomaticComplexity
private
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 976188e45c6..0c0a1a90ff2 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -20,9 +20,7 @@ module QA
verify_ssl: false
}
- RestClient::Request.execute(
- default_args.merge(args)
- )
+ RestClient::Request.execute(default_args.merge(args))
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
@@ -56,13 +54,16 @@ module QA
end
end
- def put(url, payload = nil)
+ def put(url, payload = nil, args = {})
with_retry_on_too_many_requests do
- RestClient::Request.execute(
+ default_args = {
method: :put,
url: url,
payload: payload,
- verify_ssl: false)
+ verify_ssl: false
+ }
+
+ RestClient::Request.execute(default_args.merge(args))
rescue RestClient::ExceptionWithResponse => e
return_response_or_raise(e)
end
diff --git a/qa/qa/support/formatters/feature_flag_formatter.rb b/qa/qa/support/formatters/feature_flag_formatter.rb
new file mode 100644
index 00000000000..94c039586f9
--- /dev/null
+++ b/qa/qa/support/formatters/feature_flag_formatter.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Formatters
+ class FeatureFlagFormatter < ::RSpec::Core::Formatters::BaseFormatter
+ include Specs::Helpers::FeatureFlag
+
+ ::RSpec::Core::Formatters.register(
+ self,
+ :example_group_started,
+ :example_started
+ )
+
+ # Starts example group
+ # @param [RSpec::Core::Notifications::GroupNotification] example_group_notification
+ # @return [void]
+ def example_group_started(example_group_notification)
+ group = example_group_notification.group
+
+ skip_or_run_feature_flag_tests_or_contexts(group)
+ end
+
+ # Starts example
+ # @param [RSpec::Core::Notifications::ExampleNotification] example_notification
+ # @return [void]
+ def example_started(example_notification)
+ example = example_notification.example
+
+ # if skip propagated from example_group, do not reset skip metadata
+ skip_or_run_feature_flag_tests_or_contexts(example) unless example.metadata[:skip]
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/formatters/test_stats_formatter.rb b/qa/qa/support/formatters/test_stats_formatter.rb
index 16fc0a50b1b..9d19c2e8bb5 100644
--- a/qa/qa/support/formatters/test_stats_formatter.rb
+++ b/qa/qa/support/formatters/test_stats_formatter.rb
@@ -64,6 +64,7 @@ module QA
name: example.full_description,
file_path: file_path,
status: example.execution_result.status,
+ smoke: example.metadata.key?(:smoke).to_s,
reliable: example.metadata.key?(:reliable).to_s,
quarantined: quarantined(example.metadata),
retried: ((example.metadata[:retry_attempts] || 0) > 0).to_s,
diff --git a/qa/qa/support/helpers/mask_token.rb b/qa/qa/support/helpers/mask_token.rb
new file mode 100644
index 00000000000..1f8161f7173
--- /dev/null
+++ b/qa/qa/support/helpers/mask_token.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Helpers
+ module MaskToken
+ def use_ci_variable(name:, value:, project:)
+ Resource::CiVariable.fabricate_via_api! do |ci_variable|
+ ci_variable.project = project
+ ci_variable.key = name
+ ci_variable.value = value
+ ci_variable.protected = true
+ end
+ "$#{name}"
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/helpers/plan.rb b/qa/qa/support/helpers/plan.rb
index 298a6d3f036..b6950c6bacd 100644
--- a/qa/qa/support/helpers/plan.rb
+++ b/qa/qa/support/helpers/plan.rb
@@ -57,8 +57,9 @@ module QA
}.freeze
LICENSE_TYPE = {
- license_file: 'license file',
- cloud_license: 'cloud license'
+ legacy_license: 'legacy license',
+ online_cloud: 'online license',
+ offline_cloud: 'offline license'
}.freeze
end
end
diff --git a/qa/qa/support/loglinking.rb b/qa/qa/support/loglinking.rb
index 89519e9537c..caf381912d3 100644
--- a/qa/qa/support/loglinking.rb
+++ b/qa/qa/support/loglinking.rb
@@ -5,7 +5,7 @@ module QA
# Static address variables declared for mapping environment to logging URLs
STAGING_ADDRESS = 'https://staging.gitlab.com'
STAGING_REF_ADDRESS = 'https://staging-ref.gitlab.com'
- PRODUCTION_ADDRESS = 'https://www.gitlab.com'
+ PRODUCTION_ADDRESS = 'https://gitlab.com'
PRE_PROD_ADDRESS = 'https://pre.gitlab.com'
SENTRY_ENVIRONMENTS = {
staging: 'https://sentry.gitlab.net/gitlab/staginggitlabcom/?environment=gstg',
@@ -30,7 +30,7 @@ module QA
errors = ["Correlation Id: #{correlation_id}"]
errors << "Sentry Url: #{sentry_uri}&query=correlation_id%3A%22#{correlation_id}%22" if sentry_uri
- errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20#{correlation_id}'))" if kibana_uri
+ errors << "Kibana Url: #{kibana_uri}app/discover#/?_a=(query:(language:kuery,query:'json.correlation_id%20:%20#{correlation_id}'))&_g=(time:(from:now-24h%2Fh,to:now))" if kibana_uri
errors.join("\n")
end
diff --git a/qa/qa/support/matchers/have_matcher.rb b/qa/qa/support/matchers/have_matcher.rb
index a90d2df96ae..b96566a9e5d 100644
--- a/qa/qa/support/matchers/have_matcher.rb
+++ b/qa/qa/support/matchers/have_matcher.rb
@@ -10,6 +10,7 @@ module QA
file_content
assignee
child_pipeline
+ linked_pipeline
content
design
file
diff --git a/qa/qa/support/page_error_checker.rb b/qa/qa/support/page_error_checker.rb
index ede9b49bda6..192b8c147cd 100644
--- a/qa/qa/support/page_error_checker.rb
+++ b/qa/qa/support/page_error_checker.rb
@@ -49,7 +49,7 @@ module QA
error_code = 404 if Nokogiri::HTML.parse(page.html).xpath("//img").map { |t| t[:alt] }.first.eql?('404')
# 500 error page in header surrounded by newlines, try to match
- five_hundred_test = Nokogiri::HTML.parse(page.html).xpath("//h1").map.first
+ five_hundred_test = Nokogiri::HTML.parse(page.html).xpath("//h1/text()").map.first
unless five_hundred_test.nil?
error_code = 500 if five_hundred_test.text.include?('500')
end
@@ -61,6 +61,39 @@ module QA
end
end
+ # Log request errors triggered from async api calls from the browser
+ #
+ # If any errors are found in the session, log them
+ # using QA::Runtime::Logger
+ # @param [Capybara::Session] page
+ def log_request_errors(page)
+ return if QA::Runtime::Browser.blank_page?
+
+ url = page.driver.browser.current_url
+ QA::Runtime::Logger.debug "Fetching API error cache for #{url}"
+
+ cache = page.execute_script <<~JS
+ return !(typeof(Interceptor)==="undefined") ? Interceptor.getCache() : null;
+ JS
+
+ return unless cache&.dig('errors')
+
+ grouped_errors = group_errors(cache['errors'])
+
+ errors = grouped_errors.map do |error_metadata, request_id_string|
+ "#{error_metadata} -- #{request_id_string}"
+ end
+
+ unless errors.nil? || errors.empty?
+ QA::Runtime::Logger.error "Interceptor Api Errors\n#{errors.join("\n")}"
+ end
+
+ # clear the cache after logging the errors
+ page.execute_script <<~JS
+ Interceptor && Interceptor.saveCache({});
+ JS
+ end
+
def error_report_for(logs)
logs
.map(&:message)
@@ -70,6 +103,16 @@ module QA
def logs(page)
page.driver.browser.manage.logs.get(:browser)
end
+
+ private
+
+ def group_errors(errors)
+ errors.each_with_object({}) do |error, memo|
+ url = error['url']&.split('?')&.first || 'Unknown url'
+ key = "[#{error['status']}] #{error['method']} #{url}"
+ memo[key] = "Correlation Id: #{error.dig('headers', 'x-request-id') || 'Correlation Id not found'}"
+ end
+ end
end
end
end
diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb
index 16af4bae521..89674a1d5c6 100644
--- a/qa/qa/support/wait_for_requests.rb
+++ b/qa/qa/support/wait_for_requests.rb
@@ -16,12 +16,16 @@ module QA
Waiter.wait_until(log: false) do
finished_all_ajax_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true)
end
+ QA::Support::PageErrorChecker.log_request_errors(Capybara.page) if QA::Runtime::Env.can_intercept?
rescue Repeater::WaitExceededError
raise $!, 'Page did not fully load. This could be due to an unending async request or loading icon.'
end
def finished_all_ajax_requests?
- Capybara.page.evaluate_script('window.pendingRequests || window.pendingRailsUJSRequests || 0').zero? # rubocop:disable Style/NumericPredicate
+ requests = %w[window.pendingRequests window.pendingRailsUJSRequests 0]
+ requests.unshift('(window.Interceptor && window.Interceptor.activeFetchRequests)') if Runtime::Env.can_intercept?
+ script = requests.join(' || ')
+ Capybara.page.evaluate_script(script).zero? # rubocop:disable Style/NumericPredicate
end
def finished_loading?(wait: DEFAULT_MAX_WAIT_TIME)
diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb
index 96e5690ce30..b3df6de3d54 100644
--- a/qa/qa/tools/reliable_report.rb
+++ b/qa/qa/tools/reliable_report.rb
@@ -11,6 +11,8 @@ module QA
include Support::InfluxdbTools
include Support::API
+ RELIABLE_REPORT_LABEL = "reliable test report"
+
# Project for report creation: https://gitlab.com/gitlab-org/gitlab
PROJECT_ID = 278964
@@ -28,7 +30,11 @@ module QA
reporter = new(range)
reporter.print_report
- reporter.report_in_issue_and_slack if report_in_issue_and_slack == "true"
+
+ if report_in_issue_and_slack == "true"
+ reporter.report_in_issue_and_slack
+ reporter.close_previous_reports
+ end
rescue StandardError => e
reporter&.notify_failure(e)
raise(e)
@@ -51,16 +57,15 @@ module QA
# @return [void]
def report_in_issue_and_slack
puts "Creating report".colorize(:green)
- response = post(
- "#{gitlab_api_url}/projects/#{PROJECT_ID}/issues",
- {
- title: "Reliable e2e test report",
- description: report_issue_body,
- labels: "Quality,test,type::maintenance,reliable test report,automation:ml"
- },
- headers: { "PRIVATE-TOKEN" => gitlab_access_token }
+ issue = api_update(
+ :post,
+ "projects/#{PROJECT_ID}/issues",
+ title: "Reliable e2e test report",
+ description: report_issue_body,
+ labels: "#{RELIABLE_REPORT_LABEL},Quality,test,type::maintenance,automation:ml"
)
- web_url = parse_body(response)[:web_url]
+ @report_iid = issue[:iid]
+ web_url = issue[:web_url]
puts "Created report issue: #{web_url}"
puts "Sending slack notification".colorize(:green)
@@ -76,6 +81,25 @@ module QA
puts "Done!"
end
+ # Close previous reliable test reports
+ #
+ # @return [void]
+ def close_previous_reports
+ puts "Closing previous reports".colorize(:green)
+ issues = api_get("projects/#{PROJECT_ID}/issues?labels=#{RELIABLE_REPORT_LABEL}&state=opened")
+
+ issues
+ .reject { |issue| issue[:iid] == report_iid }
+ .each do |issue|
+ issue_iid = issue[:iid]
+ issue_endpoint = "projects/#{PROJECT_ID}/issues/#{issue_iid}"
+
+ puts "Closing previous report '#{issue[:web_url]}'"
+ api_update(:put, issue_endpoint, state_event: "close")
+ api_update(:post, "#{issue_endpoint}/notes", body: "Closed issue in favor of ##{report_iid}")
+ end
+ end
+
# Notify failure
#
# @param [StandardError] error
@@ -89,7 +113,39 @@ module QA
private
- attr_reader :range, :slack_channel
+ attr_reader :range, :slack_channel, :report_iid
+
+ # Slack notifier
+ #
+ # @return [Slack::Notifier]
+ def notifier
+ @notifier ||= Slack::Notifier.new(
+ slack_webhook_url,
+ channel: slack_channel,
+ username: "Reliable Spec Report"
+ )
+ end
+
+ # Gitlab access token
+ #
+ # @return [String]
+ def gitlab_access_token
+ @gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
+ end
+
+ # Gitlab api url
+ #
+ # @return [String]
+ def gitlab_api_url
+ @gitlab_api_url ||= ENV["CI_API_V4_URL"] || raise("Missing CI_API_V4_URL env variable")
+ end
+
+ # Slack webhook url
+ #
+ # @return [String]
+ def slack_webhook_url
+ @slack_webhook_url ||= ENV["SLACK_WEBHOOK"] || raise("Missing SLACK_WEBHOOK env variable")
+ end
# Markdown formatted report issue body
#
@@ -316,6 +372,7 @@ module QA
|> filter(fn: (r) => r.status != "pending" and
r.merge_request == "false" and
r.quarantined == "false" and
+ r.smoke == "false" and
r.reliable == "#{reliable}" and
r._field == "id"
)
@@ -323,36 +380,30 @@ module QA
QUERY
end
- # Slack notifier
+ # Api get request
#
- # @return [Slack::Notifier]
- def notifier
- @notifier ||= Slack::Notifier.new(
- slack_webhook_url,
- channel: slack_channel,
- username: "Reliable Spec Report"
- )
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_get(path)
+ response = get("#{gitlab_api_url}/#{path}", { headers: { "PRIVATE-TOKEN" => gitlab_access_token } })
+ parse_body(response)
end
- # Gitlab access token
+ # Api update request
#
- # @return [String]
- def gitlab_access_token
- @gitlab_access_token ||= ENV["GITLAB_ACCESS_TOKEN"] || raise("Missing GITLAB_ACCESS_TOKEN env variable")
- end
-
- # Gitlab api url
- #
- # @return [String]
- def gitlab_api_url
- @gitlab_api_url ||= ENV["CI_API_V4_URL"] || raise("Missing CI_API_V4_URL env variable")
- end
-
- # Slack webhook url
- #
- # @return [String]
- def slack_webhook_url
- @slack_webhook_url ||= ENV["SLACK_WEBHOOK"] || raise("Missing SLACK_WEBHOOK env variable")
+ # @param [Symbol] verb :post or :put
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_update(verb, path, **payload)
+ response = send(
+ verb,
+ "#{gitlab_api_url}/#{path}",
+ payload,
+ { headers: { "PRIVATE-TOKEN" => gitlab_access_token } }
+ )
+ parse_body(response)
end
end
end
diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb
index 476f87fff6b..5218e6df217 100644
--- a/qa/qa/tools/test_resources_handler.rb
+++ b/qa/qa/tools/test_resources_handler.rb
@@ -75,11 +75,15 @@ module QA
# Download files from GCS bucket by environment name
# Delete the files afterward
def download(ci_project_name)
- files_list = gcs_storage.list_objects(BUCKET, prefix: ci_project_name).items.each_with_object([]) do |obj, arr|
+ bucket_items = gcs_storage.list_objects(BUCKET, prefix: ci_project_name).items
+
+ files_list = bucket_items&.each_with_object([]) do |obj, arr|
arr << obj.name
end
- return puts "\nNothing to download!" if files_list.empty?
+ return puts "\nNothing to download!" if files_list.blank?
+
+ FileUtils.mkdir_p('tmp/')
files_list.each do |file_name|
local_path = "tmp/#{file_name.split('/').last}"
diff --git a/qa/qa/vendor/jenkins/page/configure_job.rb b/qa/qa/vendor/jenkins/page/configure_job.rb
index 471567ec828..65719795720 100644
--- a/qa/qa/vendor/jenkins/page/configure_job.rb
+++ b/qa/qa/vendor/jenkins/page/configure_job.rb
@@ -15,6 +15,7 @@ module QA
def configure(scm_url:)
set_git_source_code_management_url(scm_url)
+ set_git_branches_to_build("*/#{Runtime::Env.default_branch}")
click_build_when_change_is_pushed_to_gitlab
set_publish_status_to_gitlab
@@ -31,6 +32,10 @@ module QA
set_repository_url(repository_url)
end
+ def set_git_branches_to_build(branches)
+ find('.setting-name', text: "Branch Specifier (blank for 'any')").find(:xpath, "..").find('input').set branches
+ end
+
def click_build_when_change_is_pushed_to_gitlab
find('label', text: 'Build when a change is pushed to GitLab').find(:xpath, "..").find('input').click
end