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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa')
-rw-r--r--qa/.confiner/master.yml15
-rw-r--r--qa/.devcontainer/devcontainer.json40
-rw-r--r--qa/.solargraph.yml.example17
-rw-r--r--qa/Dockerfile11
-rw-r--r--qa/Gemfile26
-rw-r--r--qa/Gemfile.lock76
-rw-r--r--qa/README.md11
-rw-r--r--qa/gdk/Dockerfile.gdk4
-rw-r--r--qa/lib/gitlab/page/subscriptions/new.rb4
-rw-r--r--qa/qa/ce/strategy.rb4
-rw-r--r--qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb2
-rw-r--r--qa/qa/flow/group.rb34
-rw-r--r--qa/qa/git/repository.rb2
-rw-r--r--qa/qa/mobile/page/main/menu.rb60
-rw-r--r--qa/qa/mobile/page/project/issue/show.rb10
-rw-r--r--qa/qa/mobile/page/project/show.rb31
-rw-r--r--qa/qa/mobile/page/sub_menus/common.rb28
-rw-r--r--qa/qa/page/admin/menu.rb10
-rw-r--r--qa/qa/page/component/access_tokens.rb28
-rw-r--r--qa/qa/page/component/badges.rb16
-rw-r--r--qa/qa/page/component/ci_icon.rb2
-rw-r--r--qa/qa/page/component/clone_panel.rb18
-rw-r--r--qa/qa/page/component/confirm_modal.rb10
-rw-r--r--qa/qa/page/component/delete_modal.rb10
-rw-r--r--qa/qa/page/component/groups_filter.rb12
-rw-r--r--qa/qa/page/component/members/invite_members_modal.rb26
-rw-r--r--qa/qa/page/component/members/members_table.rb28
-rw-r--r--qa/qa/page/component/members/remove_group_modal.rb10
-rw-r--r--qa/qa/page/component/members/remove_member_modal.rb10
-rw-r--r--qa/qa/page/component/namespace_select.rb15
-rw-r--r--qa/qa/page/component/project_selector.rb10
-rw-r--r--qa/qa/page/component/web_ide/alert.rb31
-rw-r--r--qa/qa/page/component/web_ide/modal/create_new_file.rb19
-rw-r--r--qa/qa/page/component/web_ide/web_terminal_panel.rb63
-rw-r--r--qa/qa/page/component/wiki.rb2
-rw-r--r--qa/qa/page/dashboard/projects.rb17
-rw-r--r--qa/qa/page/file/form.rb4
-rw-r--r--qa/qa/page/group/bulk_import.rb13
-rw-r--r--qa/qa/page/group/settings/general.rb74
-rw-r--r--qa/qa/page/group/settings/package_registries.rb18
-rw-r--r--qa/qa/page/group/show.rb22
-rw-r--r--qa/qa/page/layout/flash.rb4
-rw-r--r--qa/qa/page/layout/performance_bar.rb16
-rw-r--r--qa/qa/page/main/login.rb86
-rw-r--r--qa/qa/page/main/menu.rb66
-rw-r--r--qa/qa/page/main/oauth.rb4
-rw-r--r--qa/qa/page/main/terms.rb8
-rw-r--r--qa/qa/page/main/two_factor_auth.rb8
-rw-r--r--qa/qa/page/merge_request/show.rb34
-rw-r--r--qa/qa/page/modal/delete_wiki.rb4
-rw-r--r--qa/qa/page/profile/accounts/show.rb16
-rw-r--r--qa/qa/page/profile/password.rb4
-rw-r--r--qa/qa/page/profile/ssh_keys.rb32
-rw-r--r--qa/qa/page/profile/two_factor_auth.rb38
-rw-r--r--qa/qa/page/project/deployments/environments/index.rb21
-rw-r--r--qa/qa/page/project/fork/new.rb14
-rw-r--r--qa/qa/page/project/menu.rb1
-rw-r--r--qa/qa/page/project/monitor/alerts/show.rb4
-rw-r--r--qa/qa/page/project/pages.rb22
-rw-r--r--qa/qa/page/project/settings/auto_devops.rb8
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb12
-rw-r--r--qa/qa/page/project/settings/integrations.rb20
-rw-r--r--qa/qa/page/project/settings/pages.rb26
-rw-r--r--qa/qa/page/project/settings/runners.rb19
-rw-r--r--qa/qa/page/project/settings/services/jenkins.rb20
-rw-r--r--qa/qa/page/project/settings/services/jira.rb42
-rw-r--r--qa/qa/page/project/settings/services/pipeline_status_emails.rb12
-rw-r--r--qa/qa/page/project/settings/visibility_features_permissions.rb8
-rw-r--r--qa/qa/page/project/show.rb11
-rw-r--r--qa/qa/page/project/sub_menus/common.rb25
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb4
-rw-r--r--qa/qa/page/project/tag/index.rb4
-rw-r--r--qa/qa/page/project/tag/new.rb12
-rw-r--r--qa/qa/page/project/tag/show.rb8
-rw-r--r--qa/qa/page/project/web_ide/edit.rb349
-rw-r--r--qa/qa/page/project/web_ide/vscode.rb122
-rw-r--r--qa/qa/page/search/results.rb7
-rw-r--r--qa/qa/page/sub_menus/common.rb28
-rw-r--r--qa/qa/page/sub_menus/deploy.rb4
-rw-r--r--qa/qa/resource/project.rb16
-rw-r--r--qa/qa/resource/project_imported_from_github.rb4
-rw-r--r--qa/qa/runtime/browser.rb1
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/scenario/template.rb42
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb1
-rw-r--r--qa/qa/service/docker_run/smocker.rb2
-rw-r--r--qa/qa/specs/features/api/10_govern/group_access_token_spec.rb22
-rw-r--r--qa/qa/specs/features/api/10_govern/project_access_token_spec.rb20
-rw-r--r--qa/qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb4
-rw-r--r--qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb15
-rw-r--r--qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb76
-rw-r--r--qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb9
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb22
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb1
-rw-r--r--qa/qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb6
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb3
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb3
-rw-r--r--qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb2
-rw-r--r--qa/qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb25
-rw-r--r--qa/qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/pages/new_static_page_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb49
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb29
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/add_file_template_spec.rb79
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/create_first_file_in_web_ide_spec.rb36
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/link_to_line_in_web_ide_spec.rb46
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/open_web_ide_from_diff_tab_spec.rb76
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/review_merge_request_spec.rb46
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb36
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb8
-rw-r--r--qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb30
-rw-r--r--qa/qa/specs/helpers/feature_setup.rb126
-rw-r--r--qa/qa/specs/spec_helper.rb16
-rw-r--r--qa/qa/support/api.rb4
-rw-r--r--qa/qa/support/global_options.rb26
-rw-r--r--qa/qa/support/helpers/project.rb18
-rw-r--r--qa/qa/support/knapsack_report.rb23
-rw-r--r--qa/qa/support/run.rb4
-rw-r--r--qa/qa/support/ssh.rb2
-rw-r--r--qa/qa/tools/ci/qa_changes.rb3
-rw-r--r--qa/qa/tools/reliable_report.rb203
-rw-r--r--qa/qa/vendor/snowplow/product_analytics/event.rb176
-rw-r--r--qa/spec/fixtures/knapsack_report/instance-selective-parallel.json3
-rw-r--r--qa/spec/fixtures/knapsack_report/instance.json1
-rw-r--r--qa/spec/git/repository_spec.rb2
-rw-r--r--qa/spec/runtime/path_spec.rb27
-rw-r--r--qa/spec/scenario/template_spec.rb57
-rw-r--r--qa/spec/scenario_shared_examples.rb6
-rw-r--r--qa/spec/spec_helper.rb6
-rw-r--r--qa/spec/specs/allure_report_spec.rb2
-rw-r--r--qa/spec/specs/helpers/feature_setup_spec.rb96
-rw-r--r--qa/spec/support/knapsack_report_spec.rb9
-rw-r--r--qa/spec/support/ssh_spec.rb4
-rw-r--r--qa/spec/tools/reliable_report_spec.rb160
-rw-r--r--qa/tasks/knapsack.rake16
160 files changed, 1791 insertions, 1997 deletions
diff --git a/qa/.confiner/master.yml b/qa/.confiner/master.yml
deleted file mode 100644
index f58ea5de017..00000000000
--- a/qa/.confiner/master.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-- name: Quarantine E2E tests in Master that fail consistently
- plugin:
- name: gitlab # https://gitlab.com/gitlab-org/quality/confiner/-/blob/main/doc/plugins/gitlab.md
- args:
- threshold: 3 # 3 failures
- private_token: $QA_GITLAB_CI_TOKEN
- project_id: gitlab-org/gitlab
- target_project: gitlab-org/gitlab
- failure_issue_labels: QA,Quality
- failure_issue_prefix: "Failure in "
- pwd: qa # E2E specs reside in the qa subdirectory
- timeout: 30
- ref: master
- actions:
- - quarantine
diff --git a/qa/.devcontainer/devcontainer.json b/qa/.devcontainer/devcontainer.json
new file mode 100644
index 00000000000..99c705269c1
--- /dev/null
+++ b/qa/.devcontainer/devcontainer.json
@@ -0,0 +1,40 @@
+// this configuration starts docker container with qa code attached to docker test network
+// this assumes omnibus instance has been started beforehand using `gitlab-qa` gem
+{
+ "name": "gitlab-qa",
+ "privileged": true,
+ "build": {
+ "dockerfile": "../Dockerfile",
+ "context": "../../",
+ "target": "dev"
+ },
+ "runArgs": [
+ "--net=test"
+ ],
+ "mounts": [
+ {
+ "type": "bind",
+ "source": "/var/run/docker.sock",
+ "target": "/var/run/docker.sock"
+ }
+ ],
+ "containerEnv": {
+ "CHROME_DISABLE_DEV_SHM": "true",
+ "GITLAB_QA_ADMIN_ACCESS_TOKEN": "ypCa3Dzb23o5nvsixwPA",
+ "QA_GITLAB_URL": "${localEnv:QA_GITLAB_URL}",
+ "QA_SMOCKER_HOST": "smocker"
+ },
+ "onCreateCommand": "echo \"export QA_GITLAB_URL=${QA_GITLAB_URL:-http://$(docker ps | grep -m 1 'gitlab' | awk '{ print $NF }').test}\" >> /root/.bashrc",
+ "updateContentCommand": "cp .solargraph.yml.example .solargraph.yml",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "castwide.solargraph"
+ ],
+ "settings": {
+ "solargraph.diagnostics": true,
+ "solargraph.formatting": true
+ }
+ }
+ }
+}
diff --git a/qa/.solargraph.yml.example b/qa/.solargraph.yml.example
new file mode 100644
index 00000000000..41508b2162d
--- /dev/null
+++ b/qa/.solargraph.yml.example
@@ -0,0 +1,17 @@
+---
+include:
+- "**/*.rb"
+exclude:
+- "spec/**/*"
+- qa/specs/features/**/*
+- ".bundle/**/*"
+require: []
+domains: []
+reporters:
+- rubocop # diagnostics
+- require_not_found
+formatter:
+ rubocop: # formatting
+require_paths: []
+plugins: []
+max_files: 20000
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 92866ec66cc..1a6a670bf96 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,7 +1,7 @@
-ARG DOCKER_VERSION=20.10.14
-ARG CHROME_VERSION=106
-ARG QA_BUILD_TARGET=ee
+ARG DOCKER_VERSION=24.0.5
+ARG CHROME_VERSION=113
ARG RUBY_VERSION=3.0
+ARG QA_BUILD_TARGET=ee
FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23 AS foss
LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
@@ -70,4 +70,9 @@ ONBUILD COPY ./jh/qa /home/gitlab/jh/qa
ONBUILD COPY ./jh/lib /home/gitlab/jh/lib
ONBUILD COPY ./jh/config/feature_flags /home/gitlab/jh/config/feature_flags
+# Add solargraph gem for devcontainer
+# Solargraph is only present in parent Gemfile so we just install it manually
+FROM ee as dev
+RUN gem install solargraph --force
+
FROM $QA_BUILD_TARGET
diff --git a/qa/Gemfile b/qa/Gemfile
index 3b0e8fa888c..65ee52e44aa 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,46 +2,44 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 12', '>= 12.5.0', require: 'gitlab/qa'
-gem 'gitlab_quality-test_tooling', '~> 0.9.3', require: false
+gem 'gitlab-qa', '~> 13', '>= 13.1.0', require: 'gitlab/qa'
+gem 'gitlab_quality-test_tooling', '~> 1.9.0', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
-gem 'allure-rspec', '~> 2.20.0'
+gem 'allure-rspec', '~> 2.23.0'
gem 'capybara', '~> 3.39.2'
gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.1.0'
gem 'rspec', '~> 3.12'
-gem 'selenium-webdriver', '= 4.14.0'
+gem 'selenium-webdriver', '= 4.16.0'
gem 'airborne', '~> 0.3.7', require: false # airborne is messing with rspec sandboxed mode so not requiring by default
gem 'rest-client', '~> 2.1.0'
gem 'rspec-retry', '~> 0.6.2', require: 'rspec/retry'
gem 'rspec_junit_formatter', '~> 0.6.0'
-gem 'faker', '~> 3.2'
+gem 'faker', '~> 3.2', '>= 3.2.2'
gem 'knapsack', '~> 4.0'
-gem 'parallel_tests', '~> 4.2', '>= 4.2.1'
+gem 'parallel_tests', '~> 4.3'
gem 'rotp', '~> 6.3.0'
-gem 'parallel', '~> 1.23'
+gem 'parallel', '~> 1.24'
gem 'rainbow', '~> 3.1.1'
gem 'rspec-parameterized', '~> 1.0.0'
-gem 'octokit', '~> 7.2.0'
+gem 'octokit', '~> 8.0.0'
gem "faraday-retry", "~> 2.2"
-gem 'zeitwerk', '~> 2.6', '>= 2.6.8'
-gem 'influxdb-client', '~> 2.9'
+gem 'zeitwerk', '~> 2.6', '>= 2.6.12'
+gem 'influxdb-client', '~> 3.0'
gem 'terminal-table', '~> 3.0.2', require: false
gem 'slack-notifier', '~> 2.4', require: false
gem 'fog-google', '~> 1.19', require: false
gem 'fog-core', '2.1.0', require: false # fog-google generates a ton of warnings with latest core
gem "warning", "~> 1.3"
-gem 'confiner', '~> 0.4'
-
gem 'chemlab', '~> 0.11', '>= 0.11.1'
gem 'chemlab-library-www-gitlab-com', '~> 0.1', '>= 0.1.1'
# dependencies for jenkins client
-gem 'nokogiri', '~> 1.15', '>= 1.15.4'
+gem 'nokogiri', '~> 1.15', '>= 1.15.5'
-gem 'deprecation_toolkit', '~> 2.0.3', require: false
+gem 'deprecation_toolkit', '~> 2.0.4', require: false
gem 'factory_bot', '~> 6.3.0'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index a1563a7351e..eb847eb04ec 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -30,15 +30,17 @@ GEM
rack-test (>= 1.1.0, < 2.0)
rest-client (>= 2.0.2, < 3.0)
rspec (~> 3.8)
- allure-rspec (2.20.0)
- allure-ruby-commons (= 2.20.0)
+ allure-rspec (2.23.0)
+ allure-ruby-commons (= 2.23.0)
rspec-core (>= 3.8, < 4)
- allure-ruby-commons (2.20.0)
+ allure-ruby-commons (2.23.0)
mime-types (>= 3.3, < 4)
- oj (>= 3.10, < 4)
require_all (>= 2, < 4)
rspec-expectations (~> 3.12)
uuid (>= 2.3, < 3)
+ amatch (0.4.1)
+ mize
+ tins (~> 1.0)
ast (2.4.2)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
@@ -67,13 +69,10 @@ GEM
coderay (1.1.2)
colorize (0.8.1)
concurrent-ruby (1.2.2)
- confiner (0.4.0)
- gitlab (>= 4.17)
- zeitwerk (>= 2.5, < 3)
crass (1.0.6)
debug_inspector (1.1.0)
declarative (0.0.20)
- deprecation_toolkit (2.0.3)
+ deprecation_toolkit (2.0.4)
activesupport (>= 5.2)
diff-lcs (1.3)
domain_name (0.5.20190701)
@@ -82,7 +81,7 @@ GEM
excon (0.92.4)
factory_bot (6.3.0)
activesupport (>= 5.0.0)
- faker (3.2.0)
+ faker (3.2.2)
i18n (>= 1.8.11, < 2)
faraday (2.5.2)
faraday-net_http (>= 2.0, < 3.1)
@@ -121,7 +120,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (12.5.0)
+ gitlab-qa (13.1.0)
activesupport (>= 6.1, < 7.1)
gitlab (~> 4.19)
http (~> 5.0)
@@ -130,8 +129,9 @@ GEM
rainbow (>= 3, < 4)
table_print (= 1.5.7)
zeitwerk (>= 2, < 3)
- gitlab_quality-test_tooling (0.9.3)
- activesupport (>= 6.1, < 7.1)
+ gitlab_quality-test_tooling (1.9.0)
+ activesupport (>= 6.1, < 7.2)
+ amatch (~> 0.4.1)
gitlab (~> 4.19)
http (~> 5.0)
nokogiri (~> 1.10)
@@ -186,7 +186,7 @@ GEM
httpclient (2.8.3)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
- influxdb-client (2.9.0)
+ influxdb-client (3.0.0)
jwt (2.5.0)
knapsack (4.0.0)
rake
@@ -209,19 +209,20 @@ GEM
mini_mime (1.1.0)
mini_portile2 (2.8.2)
minitest (5.20.0)
+ mize (0.4.1)
+ protocol (~> 2.0)
multi_json (1.15.0)
multi_xml (0.6.0)
netrc (0.11.0)
- nokogiri (1.15.4)
+ nokogiri (1.15.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- octokit (7.2.0)
+ octokit (8.0.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
- oj (3.13.23)
os (1.1.4)
- parallel (1.23.0)
- parallel_tests (4.2.1)
+ parallel (1.24.0)
+ parallel_tests (4.3.0)
parallel
parser (3.2.2.1)
ast (~> 2.4.1)
@@ -229,6 +230,8 @@ GEM
coderay
parser
unparser
+ protocol (2.0.0)
+ ruby_parser (~> 3.0)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
@@ -292,24 +295,30 @@ GEM
ruby-debug-ide (0.7.3)
rake (>= 0.8.1)
ruby2_keywords (0.0.5)
+ ruby_parser (3.20.3)
+ sexp_processor (~> 4.16)
rubyzip (2.3.2)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
- selenium-webdriver (4.14.0)
+ selenium-webdriver (4.16.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
+ sexp_processor (4.17.0)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
slack-notifier (2.4.0)
+ sync (0.5.0)
systemu (2.6.5)
table_print (1.5.7)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
+ tins (1.32.1)
+ sync
trailblazer-option (0.1.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
@@ -331,7 +340,7 @@ GEM
websocket (1.2.10)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.6.8)
+ zeitwerk (2.6.12)
PLATFORMS
ruby
@@ -339,27 +348,26 @@ PLATFORMS
DEPENDENCIES
activesupport (~> 7.0.8)
airborne (~> 0.3.7)
- allure-rspec (~> 2.20.0)
+ allure-rspec (~> 2.23.0)
capybara (~> 3.39.2)
capybara-screenshot (~> 1.0.26)
chemlab (~> 0.11, >= 0.11.1)
chemlab-library-www-gitlab-com (~> 0.1, >= 0.1.1)
- confiner (~> 0.4)
- deprecation_toolkit (~> 2.0.3)
+ deprecation_toolkit (~> 2.0.4)
factory_bot (~> 6.3.0)
- faker (~> 3.2)
+ faker (~> 3.2, >= 3.2.2)
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 12, >= 12.5.0)
+ gitlab-qa (~> 13, >= 13.1.0)
gitlab-utils!
- gitlab_quality-test_tooling (~> 0.9.3)
- influxdb-client (~> 2.9)
+ gitlab_quality-test_tooling (~> 1.9.0)
+ influxdb-client (~> 3.0)
knapsack (~> 4.0)
- nokogiri (~> 1.15, >= 1.15.4)
- octokit (~> 7.2.0)
- parallel (~> 1.23)
- parallel_tests (~> 4.2, >= 4.2.1)
+ nokogiri (~> 1.15, >= 1.15.5)
+ octokit (~> 8.0.0)
+ parallel (~> 1.24)
+ parallel_tests (~> 4.3)
pry-byebug (~> 3.10.1)
rainbow (~> 3.1.1)
rake (~> 13, >= 13.1.0)
@@ -370,11 +378,11 @@ DEPENDENCIES
rspec-retry (~> 0.6.2)
rspec_junit_formatter (~> 0.6.0)
ruby-debug-ide (~> 0.7.3)
- selenium-webdriver (= 4.14.0)
+ selenium-webdriver (= 4.16.0)
slack-notifier (~> 2.4)
terminal-table (~> 3.0.2)
warning (~> 1.3)
- zeitwerk (~> 2.6, >= 2.6.8)
+ zeitwerk (~> 2.6, >= 2.6.12)
BUNDLED WITH
- 2.4.21
+ 2.5.1
diff --git a/qa/README.md b/qa/README.md
index ee9f1128f70..da845108b18 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -104,11 +104,22 @@ bundle exec rspec <path/to/spec.rb>
```
Note:
+
- If you want to run tests requiring SSH against GDK, you will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
- If this is your first time running GDK, you can use the password pre-set for `root`. [See supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables). If you have changed your `root` password, export the password as `GITLAB_INITIAL_ROOT_PASSWORD`.
- By default the tests will run in a headless browser. If you'd like to watch the test execution, you can export `WEBDRIVER_HEADLESS=false`.
- Tests that are tagged `:orchestrated` require special setup (e.g., custom GitLab configuration, or additional services such as LDAP). All [orchestrated tests can be run via `gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md). There are also [setup instructions](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) for running some of those tests against GDK or another local GitLab instance.
+#### Remote development
+
+For [VSCode](https://code.visualstudio.com/) user, [.devcontainer](.devcontainer/devcontainer.json) defines configuration to develop E2E tests inside a Docker container which by default is attached to the same network as environments started by [`gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa) gem. For more information on how to use `dev containers`, see [tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial).
+
+This is useful when developing E2E tests that require GitLab instance with specific omnibus configuration. Typical workflow example:
+
+- Start `GitLab` omnibus instance with specific configuration without running tests, for example: `gitlab-qa Test::Integration::Import EE --no-tests`. For available configurations, see [docs](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md)
+- Start dev container from `VSCode` environment
+- Develop and run tests from within the container which will automatically execute against started GitLab instance
+
#### Generic command for a typical GDK installation
The following is an example command you can use if you have configured GDK to run on a specific IP address and port, with a username, password, and personal access token that aren't the defaults, and you would like the test framework to show debug logs:
diff --git a/qa/gdk/Dockerfile.gdk b/qa/gdk/Dockerfile.gdk
index bc3c1c4317d..61f682e6399 100644
--- a/qa/gdk/Dockerfile.gdk
+++ b/qa/gdk/Dockerfile.gdk
@@ -5,7 +5,7 @@ ENV GITLAB_LICENSE_MODE=test \
# Clone GDK at specific sha and bootstrap packages
#
-ARG GDK_SHA=e2e32c98ef2874a3bd13af00e0085f8299ff3288
+ARG GDK_SHA=768deeebc94e37ba103dfec8102e794ba0f22c4e
RUN set -eux; \
git clone --depth 1 https://gitlab.com/gitlab-org/gitlab-development-kit.git && cd gitlab-development-kit; \
git fetch --depth 1 origin ${GDK_SHA} && git -c advice.detachedHead=false checkout ${GDK_SHA}; \
@@ -106,6 +106,6 @@ ENTRYPOINT [ "/home/gdk/entrypoint" ]
CMD [ "gdk", "tail" ]
HEALTHCHECK --interval=10s --timeout=1s --start-period=5s --retries=17 \
- CMD curl --fail http://0.0.0.0:3000/users/sign_in || exit 1
+ CMD curl --fail http://gdk.test:3000/users/sign_in || exit 1
EXPOSE 3000
diff --git a/qa/lib/gitlab/page/subscriptions/new.rb b/qa/lib/gitlab/page/subscriptions/new.rb
index 739efeed898..05dd1ae82ba 100644
--- a/qa/lib/gitlab/page/subscriptions/new.rb
+++ b/qa/lib/gitlab/page/subscriptions/new.rb
@@ -46,10 +46,12 @@ module Gitlab
def purchase
::QA::Support::Retrier.retry_until(
- max_duration: 60,
+ max_duration: 80,
sleep_interval: 10,
message: 'Expected no Zuora lock competition error'
) do
+ ::QA::Runtime::Logger.debug('Attempting to purchase subscription')
+
confirm_purchase
::QA::Support::WaitForRequests.wait_for_requests
!lock_competition_error?
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index 24398e8eace..d936640abfb 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -20,10 +20,10 @@ module QA
)
end
- QA::Runtime::Logger.info("Browser: #{QA::Runtime::Env.browser}")
+ QA::Runtime::Logger.info("Using Browser: #{QA::Runtime::Env.browser}")
if QA::Runtime::Env.use_selenoid?
- QA::Runtime::Logger.info("Browser version: #{QA::Runtime::Env.selenoid_browser_version}")
+ QA::Runtime::Logger.info("Using Selenoid Browser version: #{QA::Runtime::Env.selenoid_browser_version}")
end
# The login page could take some time to load the first time it is visited.
diff --git a/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb
index 39c04f6511b..8ad3cc50330 100644
--- a/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb
+++ b/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb
@@ -9,4 +9,4 @@ test_package:
- "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload <%= package.name %>/0.1@mycompany/stable --all --remote=gitlab"
- conan install <%= package.name %>/0.1@mycompany/stable --remote=gitlab
tags:
- - runner-for-<%= project.name %> \ No newline at end of file
+ - runner-for-<%= project.name %>
diff --git a/qa/qa/flow/group.rb b/qa/qa/flow/group.rb
new file mode 100644
index 00000000000..060b196ba84
--- /dev/null
+++ b/qa/qa/flow/group.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module QA
+ module Flow
+ module Group
+ extend self
+
+ def update_to_ultimate(group)
+ Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Admin::Menu.perform(&:go_to_groups_overview)
+
+ Page::Admin::Overview::Groups::Index.perform do |index|
+ index.search_group(group.name)
+ index.click_group(group.name)
+ end
+
+ return unless EE::Page::Admin::Overview::Groups::Show.perform(&:group_plan) != 'Ultimate'
+
+ Page::Admin::Overview::Groups::Show.perform(&:click_edit_group_link)
+
+ Page::Admin::Overview::Groups::Edit.perform do |edit|
+ edit.select_plan('Ultimate')
+ edit.click_save_changes_button
+ end
+ end
+
+ def enable_product_analytics(group)
+ group.visit!
+ Page::Group::Menu.perform(&:go_to_general_settings)
+ Page::Group::Settings::General.perform(&:set_use_product_analytics_enabled)
+ end
+ end
+ end
+end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 0e8a1c68bc5..966cc596a08 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -331,7 +331,7 @@ module QA
end
def run_git(command_str, env: env_vars, max_attempts: 1)
- run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ')
+ run(command_str, env: env, max_attempts: max_attempts, sleep_internal: 10, log_prefix: 'Git: ')
end
end
end
diff --git a/qa/qa/mobile/page/main/menu.rb b/qa/qa/mobile/page/main/menu.rb
deleted file mode 100644
index 73d3b9f7982..00000000000
--- a/qa/qa/mobile/page/main/menu.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Mobile
- module Page
- module Main
- module Menu
- extend QA::Page::PageConcern
-
- def self.prepended(base)
- super
-
- base.class_eval do
- view 'app/views/layouts/header/_default.html.haml' do
- element :mobile_navbar_button, required: true
- end
-
- view 'app/assets/javascripts/nav/components/responsive_home.vue' do
- element :mobile_new_dropdown
- end
- end
- end
-
- def open_mobile_menu
- if has_no_element?(:user_avatar_content)
- Support::Retrier.retry_until do
- click_element(:mobile_navbar_button)
- has_element?(:user_avatar_content)
- end
- end
- end
-
- def open_mobile_new_dropdown
- open_mobile_menu
-
- Support::Retrier.retry_until do
- find('[data-qa-selector="mobile_new_dropdown"] > button').click
- has_css?('.dropdown-menu-right.show')
- end
- end
-
- def has_personal_area?(wait: Capybara.default_max_wait_time)
- open_mobile_menu
- super
- end
-
- def has_no_personal_area?(wait: Capybara.default_max_wait_time)
- open_mobile_menu
- super
- end
-
- def within_user_menu
- open_mobile_menu
- super
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/mobile/page/project/issue/show.rb b/qa/qa/mobile/page/project/issue/show.rb
index 4184c9871cc..e458883d332 100644
--- a/qa/qa/mobile/page/project/issue/show.rb
+++ b/qa/qa/mobile/page/project/issue/show.rb
@@ -13,21 +13,21 @@ module QA
base.class_eval do
view 'app/assets/javascripts/issues/show/components/header_actions.vue' do
- element :mobile_dropdown
- element :mobile_close_issue_button
- element :mobile_reopen_issue_button
+ element 'mobile-dropdown'
+ element 'mobile-close-issue-button'
+ element 'mobile-reopen-issue-button'
end
end
end
def click_close_issue_button
find('[data-testid="mobile-dropdown"] > button').click
- find_element(:mobile_close_issue_button, visible: false).click
+ find_element('mobile-close-issue-button', visible: false).click
end
def has_reopen_issue_button?
find('[data-testid="mobile-dropdown"] > button').click
- has_element?(:mobile_reopen_issue_button)
+ has_element?('mobile-reopen-issue-button')
end
end
end
diff --git a/qa/qa/mobile/page/project/show.rb b/qa/qa/mobile/page/project/show.rb
deleted file mode 100644
index 8a0a222c852..00000000000
--- a/qa/qa/mobile/page/project/show.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Mobile
- module Page
- module Project
- module Show
- extend QA::Page::PageConcern
-
- def self.prepended(base)
- super
-
- base.class_eval do
- prepend QA::Mobile::Page::Main::Menu
-
- view 'app/assets/javascripts/nav/components/top_nav_new_dropdown.vue' do
- element :new_issue_mobile_button
- end
- end
- end
-
- def go_to_new_issue
- open_mobile_new_dropdown
-
- click_element(:new_issue_mobile_button)
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/mobile/page/sub_menus/common.rb b/qa/qa/mobile/page/sub_menus/common.rb
deleted file mode 100644
index ef2815ca777..00000000000
--- a/qa/qa/mobile/page/sub_menus/common.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Mobile
- module Page
- module SubMenus
- module Common
- def open_mobile_nav_sidebar
- unless has_css?('.sidebar-expanded-mobile')
- Support::Retrier.retry_until do
- click_element('toggle-mobile-nav-button')
- has_css?('.sidebar-expanded-mobile')
- end
- end
- end
-
- def within_sidebar
- wait_for_requests
-
- open_mobile_nav_sidebar
-
- super
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index fea44a0ebca..e4b802e1015 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -23,16 +23,6 @@ module QA
def go_to_applications
click_element('nav-item-link', submenu_item: 'Applications')
end
-
- private
-
- def within_sidebar(&block)
- page.within('.sidebar-top-level-items', &block)
- end
-
- def within_submenu(element, &block)
- within_element(element, &block)
- end
end
end
end
diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb
index c80df535aba..ef920e6f536 100644
--- a/qa/qa/page/component/access_tokens.rb
+++ b/qa/qa/page/component/access_tokens.rb
@@ -15,12 +15,12 @@ module QA
end
base.view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do
- element :expiry_date_field
+ element 'expiry-date-field'
end
base.view 'app/views/shared/access_tokens/_form.html.haml' do
- element :access_token_name_field
- element :create_token_button
+ element 'access-token-name-field'
+ element 'create-token-button'
end
base.view 'app/views/shared/tokens/_scopes_form.html.haml' do
@@ -28,19 +28,19 @@ module QA
end
base.view 'app/assets/javascripts/access_tokens/components/new_access_token_app.vue' do
- element :access_token_section
- element :created_access_token_field
+ element 'access-token-section'
+ element 'created-access-token-field'
end
base.view 'app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue' do
- element :toggle_visibility_button
+ element 'toggle-visibility-button'
end
base.view 'app/assets/javascripts/access_tokens/components/access_token_table_app.vue' do
- element :revoke_button
+ element 'revoke-button'
end
- base.view 'app/views/profiles/personal_access_tokens/index.html.haml' do
+ base.view 'app/views/user_settings/personal_access_tokens/index.html.haml' do
element 'add-new-token-button'
end
@@ -62,7 +62,7 @@ module QA
end
def fill_token_name(name)
- fill_element(:access_token_name_field, name)
+ fill_element('access-token-name-field', name)
end
def check_api
@@ -70,12 +70,12 @@ module QA
end
def click_create_token_button
- click_element(:create_token_button)
+ click_element('create-token-button')
end
def created_access_token
- within_element(:access_token_section) do
- find_element(:created_access_token_field).value
+ within_element('access-token-section') do
+ find_element('created-access-token-field').value
end
end
@@ -87,7 +87,7 @@ module QA
raise "Expiry date must be in YYYY-MM-DD format"
end
- fill_element(:expiry_date_field, date)
+ fill_element('expiry-date-field', date)
end
def has_token_row_for_name?(token_name)
@@ -100,7 +100,7 @@ module QA
def revoke_first_token_with_name(token_name)
within first_token_row_for_name(token_name) do
- click_element(:revoke_button)
+ click_element('revoke-button')
end
click_confirmation_ok_button
diff --git a/qa/qa/page/component/badges.rb b/qa/qa/page/component/badges.rb
index 8c0907d17b4..118e5072f0f 100644
--- a/qa/qa/page/component/badges.rb
+++ b/qa/qa/page/component/badges.rb
@@ -9,10 +9,10 @@ module QA
end
view 'app/assets/javascripts/badges/components/badge_form.vue' do
- element :badge_name_field
- element :badge_link_url_field
- element :badge_image_url_field
- element :add_badge_button
+ element 'badge-name-field'
+ element 'badge-link-url-field'
+ element 'badge-image-url-field'
+ element 'add-badge-button'
end
view 'app/assets/javascripts/badges/components/badge_list.vue' do
@@ -29,19 +29,19 @@ module QA
end
def fill_name(name)
- fill_element :badge_name_field, name
+ fill_element 'badge-name-field', name
end
def fill_link_url(url)
- fill_element :badge_link_url_field, url
+ fill_element 'badge-link-url-field', url
end
def fill_image_url(url)
- fill_element :badge_image_url_field, url
+ fill_element 'badge-image-url-field', url
end
def click_add_badge_button
- click_element :add_badge_button
+ click_element 'add-badge-button'
end
def has_badge?(badge_name)
diff --git a/qa/qa/page/component/ci_icon.rb b/qa/qa/page/component/ci_icon.rb
index 1ddcc810f95..5b54c26fd86 100644
--- a/qa/qa/page/component/ci_icon.rb
+++ b/qa/qa/page/component/ci_icon.rb
@@ -33,7 +33,7 @@ module QA
def self.included(base)
super
- base.view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
+ base.view 'app/assets/javascripts/vue_shared/components/ci_icon/ci_icon.vue' do
element 'ci-icon-text'
end
end
diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb
index 3ea29ff63da..4d239040faa 100644
--- a/qa/qa/page/component/clone_panel.rb
+++ b/qa/qa/page/component/clone_panel.rb
@@ -9,29 +9,29 @@ module QA
def self.included(base)
super
- base.view 'app/views/projects/buttons/_clone.html.haml' do
- element :clone_dropdown
- element :clone_dropdown_content
- element :ssh_clone_url_content
- element :http_clone_url_content
+ base.view 'app/views/projects/buttons/_code.html.haml' do
+ element 'clone-dropdown'
+ element 'clone-dropdown-content'
+ element 'ssh-clone-url-content'
+ element 'http-clone-url-content'
end
end
def repository_clone_http_location
- repository_clone_location(:http_clone_url_content)
+ repository_clone_location('http-clone-url-content')
end
def repository_clone_ssh_location
- repository_clone_location(:ssh_clone_url_content)
+ repository_clone_location('ssh-clone-url-content')
end
private
def repository_clone_location(kind)
wait_until(reload: false) do
- click_element :clone_dropdown
+ click_element 'clone-dropdown'
- within_element :clone_dropdown_content do
+ within_element 'clone-dropdown-content' do
Git::Location.new(find_element(kind).value)
end
end
diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb
index 26d06ecaa22..fbc3cfa9aed 100644
--- a/qa/qa/page/component/confirm_modal.rb
+++ b/qa/qa/page/component/confirm_modal.rb
@@ -15,24 +15,24 @@ module QA
end
base.view 'app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue' do
- element :confirm_danger_modal_button
- element :confirm_danger_field
+ element 'confirm-danger-modal-button'
+ element 'confirm-danger-field'
end
end
def fill_confirmation_text(text)
- fill_element(:confirm_danger_field, text)
+ fill_element('confirm-danger-field', text)
end
def wait_for_confirm_button_enabled
wait_until(reload: false) do
- !find_element(:confirm_danger_modal_button).disabled?
+ !find_element('confirm-danger-modal-button').disabled?
end
end
def confirm_transfer
wait_for_confirm_button_enabled
- click_element(:confirm_danger_modal_button)
+ click_element('confirm-danger-modal-button')
end
def click_confirmation_ok_button
diff --git a/qa/qa/page/component/delete_modal.rb b/qa/qa/page/component/delete_modal.rb
index 9fbbd9930a9..5d8924e1856 100644
--- a/qa/qa/page/component/delete_modal.rb
+++ b/qa/qa/page/component/delete_modal.rb
@@ -10,24 +10,24 @@ module QA
super
base.view 'app/assets/javascripts/projects/components/shared/delete_modal.vue' do
- element :confirm_name_field
- element :confirm_delete_button
+ element 'confirm-name-field'
+ element 'confirm-delete-button'
end
end
def fill_confirmation_path(text)
- fill_element(:confirm_name_field, text)
+ fill_element('confirm-name-field', text)
end
def wait_for_delete_button_enabled
wait_until(reload: false) do
- !find_element(:confirm_delete_button).disabled?
+ !find_element('confirm-delete-button').disabled?
end
end
def confirm_delete
wait_for_delete_button_enabled
- click_element(:confirm_delete_button)
+ click_element('confirm-delete-button')
end
end
end
diff --git a/qa/qa/page/component/groups_filter.rb b/qa/qa/page/component/groups_filter.rb
index 266c0dfa1af..b744ad9d62c 100644
--- a/qa/qa/page/component/groups_filter.rb
+++ b/qa/qa/page/component/groups_filter.rb
@@ -10,11 +10,15 @@ module QA
super
base.view 'app/assets/javascripts/groups/components/overview_tabs.vue' do
- element :groups_filter_field
+ element 'groups-filter-field'
+ end
+
+ base.view 'app/views/shared/groups/_search_form.html.haml' do
+ element 'groups-filter-field'
end
base.view 'app/assets/javascripts/groups/components/groups.vue' do
- element :groups_list_tree_container
+ element 'groups-list-tree-container'
end
end
@@ -33,10 +37,10 @@ module QA
# @param name [String] group name
# @return [Boolean] whether the filter returned any group
def filter_group(name)
- fill_element(:groups_filter_field, name).send_keys(:return)
+ fill_element('groups-filter-field', name).send_keys(:return)
# Loading starts a moment after `return` is sent. We mustn't jump ahead
wait_for_requests if spinner_exists?
- has_element?(:groups_list_tree_container, wait: 1)
+ has_element?('groups-list-tree-container', wait: 1)
end
end
end
diff --git a/qa/qa/page/component/members/invite_members_modal.rb b/qa/qa/page/component/members/invite_members_modal.rb
index b9d0b382ba1..0f91fd499fa 100644
--- a/qa/qa/page/component/members/invite_members_modal.rb
+++ b/qa/qa/page/component/members/invite_members_modal.rb
@@ -12,37 +12,37 @@ module QA
super
base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do
- element :invite_button
- element :access_level_dropdown
+ element 'invite-modal-submit'
+ element 'access-level-dropdown'
element 'invite-modal'
end
base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do
- element :members_token_select_input
+ element 'members-token-select-input'
end
base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do
- element :invite_a_group_button
+ element 'invite-group-button'
end
- base.view 'app/assets/javascripts/invite_members/constants.js' do
- element :invite_members_button
+ base.view 'app/assets/javascripts/invite_members/components/invite_members_trigger.vue' do
+ element 'invite-members-button'
end
end
def open_invite_members_modal
- click_element :invite_members_button
+ click_element 'invite-members-button'
end
def open_invite_group_modal
- click_element :invite_a_group_button
+ click_element 'invite-group-button'
end
def add_member(username, access_level = 'Developer', refresh_page: true)
open_invite_members_modal
within_element('invite-modal') do
- fill_element(:members_token_select_input, username)
+ fill_element('members-token-select-input', username)
Support::WaitForRequests.wait_for_requests
click_button(username, match: :prefer_exact)
set_access_level(access_level)
@@ -68,7 +68,7 @@ module QA
end
def send_invite(refresh = false)
- click_element :invite_button
+ click_element 'invite-modal-submit'
Support::WaitForRequests.wait_for_requests
page.refresh if refresh
end
@@ -76,8 +76,10 @@ module QA
private
def set_access_level(access_level)
- # Guest option is selected by default, skipping these steps if desired option is 'Guest'
- select_element(:access_level_dropdown, access_level) unless access_level == 'Guest'
+ within_element('access-level-dropdown') do
+ expand_select_list
+ select_item access_level
+ end
end
end
end
diff --git a/qa/qa/page/component/members/members_table.rb b/qa/qa/page/component/members/members_table.rb
index 8bc20da83af..7d6431d7b32 100644
--- a/qa/qa/page/component/members/members_table.rb
+++ b/qa/qa/page/component/members/members_table.rb
@@ -18,12 +18,12 @@ module QA
end
base.view 'app/assets/javascripts/members/components/table/members_table.vue' do
- element :member_row
+ element 'members-table'
end
- base.view 'app/assets/javascripts/members/components/table/role_dropdown.vue' do
- element :access_level_dropdown
- element :access_level_link
+ base.view 'app/assets/javascripts/members/components/table/max_role.vue' do
+ element 'access-level-dropdown'
+ element 'access-level-link'
end
base.view 'app/assets/javascripts/members/components/action_dropdowns/user_action_dropdown.vue' do
@@ -43,23 +43,23 @@ module QA
end
base.view 'app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue' do
- element :remove_group_link_button
+ element('remove-group-link-button')
end
end
def update_access_level(username, access_level)
search_member(username)
- within_element(:member_row, text: username) do
- click_element :access_level_dropdown
- click_element :access_level_link, text: access_level
+ within_element('members-table', text: username) do
+ click_element('access-level-dropdown')
+ click_element('access-level-link', text: access_level)
end
click_confirmation_ok_button_if_present
end
def remove_member(username)
- within_element(:member_row, text: username) do
+ within_element('members-table', text: username) do
click_element 'user-action-dropdown'
click_element 'delete-member-dropdown-item'
end
@@ -68,13 +68,13 @@ module QA
end
def approve_access_request(username)
- within_element(:member_row, text: username) do
+ within_element('members-table', text: username) do
click_element 'approve-access-request-button'
end
end
def deny_access_request(username)
- within_element(:member_row, text: username) do
+ within_element('members-table', text: username) do
click_element 'delete-member-button'
end
@@ -84,8 +84,8 @@ module QA
def remove_group(group_name)
click_element 'groups-list-tab'
- within_element(:member_row, text: group_name) do
- click_element :remove_group_link_button
+ within_element('members-table', text: group_name) do
+ click_element('remove-group-link-button')
end
confirm_remove_group
@@ -93,7 +93,7 @@ module QA
def has_group?(group_name)
click_element 'groups-list-tab'
- has_element?(:member_row, text: group_name)
+ has_element?('members-table', text: group_name)
end
end
end
diff --git a/qa/qa/page/component/members/remove_group_modal.rb b/qa/qa/page/component/members/remove_group_modal.rb
index 03c51757b62..b2be79604b6 100644
--- a/qa/qa/page/component/members/remove_group_modal.rb
+++ b/qa/qa/page/component/members/remove_group_modal.rb
@@ -11,16 +11,16 @@ module QA
super
base.view 'app/assets/javascripts/members/components/modals/remove_group_link_modal.vue' do
- element :remove_group_link_modal_content
- element :remove_group_button
+ element('remove-group-link-modal-content')
+ element('remove-group-button')
end
end
def confirm_remove_group
- within_element(:remove_group_link_modal_content) do
+ within_element('remove-group-link-modal-content') do
wait_for_enabled_remove_group_button
- click_element :remove_group_button
+ click_element('remove-group-button')
end
end
@@ -28,7 +28,7 @@ module QA
def wait_for_enabled_remove_group_button
retry_until(sleep_interval: 1, message: 'Waiting for remove group button to be enabled') do
- has_element?(:remove_group_button, disabled: false, wait: 3)
+ has_element?('remove-group-button', disabled: false, wait: 3)
end
end
end
diff --git a/qa/qa/page/component/members/remove_member_modal.rb b/qa/qa/page/component/members/remove_member_modal.rb
index b7b81040ba7..414e1ba5b08 100644
--- a/qa/qa/page/component/members/remove_member_modal.rb
+++ b/qa/qa/page/component/members/remove_member_modal.rb
@@ -11,16 +11,16 @@ module QA
super
base.view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do
- element :remove_member_modal
- element :remove_member_button
+ element 'remove-member-modal'
+ element 'remove-member-button'
end
end
def confirm_remove_member
- within_element(:remove_member_modal) do
+ within_element('remove-member-modal') do
wait_for_enabled_remove_member_button
- click_element :remove_member_button
+ click_element('remove-member-button')
end
end
@@ -28,7 +28,7 @@ module QA
def wait_for_enabled_remove_member_button
retry_until(sleep_interval: 1, message: 'Waiting for remove member button to be enabled') do
- has_element?(:remove_member_button, disabled: false, wait: 3)
+ has_element?('remove-member-button', disabled: false, wait: 3)
end
end
end
diff --git a/qa/qa/page/component/namespace_select.rb b/qa/qa/page/component/namespace_select.rb
index 8fb0bb79ab3..8d39f38d9db 100644
--- a/qa/qa/page/component/namespace_select.rb
+++ b/qa/qa/page/component/namespace_select.rb
@@ -10,24 +10,23 @@ module QA
super
base.view "app/assets/javascripts/groups_projects/components/transfer_locations.vue" do
- element :namespaces_list
- element :namespaces_list_groups
- element :namespaces_list_item
- element :namespaces_list_search
+ element 'transfer-locations-dropdown'
+ element 'transfer-locations-search'
+ element 'group-transfer-item'
end
end
def select_namespace(item)
- click_element :namespaces_list
+ click_element 'transfer-locations-dropdown'
- within_element(:namespaces_list) do
- fill_element(:namespaces_list_search, item)
+ within_element('transfer-locations-dropdown') do
+ fill_element('transfer-locations-search', item)
wait_for_requests
# Click element by JS in case dropdown changes position mid-click
# Workaround for issue https://gitlab.com/gitlab-org/gitlab/-/issues/381376
- namespace = find_element(:namespaces_list_item, text: item, visible: false)
+ namespace = find_element('group-transfer-item', text: item, visible: false)
click_by_javascript(namespace)
end
end
diff --git a/qa/qa/page/component/project_selector.rb b/qa/qa/page/component/project_selector.rb
index 54bd95c5422..6a6c42a1afa 100644
--- a/qa/qa/page/component/project_selector.rb
+++ b/qa/qa/page/component/project_selector.rb
@@ -10,20 +10,20 @@ module QA
super
base.view 'app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue' do
- element :project_search_field
- element :project_list_item
+ element 'project-search-field'
+ element 'project-list-item'
end
end
def fill_project_search_input(project_name)
- fill_element :project_search_field, project_name
+ fill_element 'project-search-field', project_name
end
def select_project
wait_until(sleep_interval: 2, reload: false) do
- has_element? :project_list_item
+ has_element? 'project-list-item'
end
- click_element :project_list_item
+ click_element 'project-list-item'
end
end
end
diff --git a/qa/qa/page/component/web_ide/alert.rb b/qa/qa/page/component/web_ide/alert.rb
deleted file mode 100644
index c2903662b52..00000000000
--- a/qa/qa/page/component/web_ide/alert.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Component
- module WebIDE
- module Alert
- extend QA::Page::PageConcern
-
- def self.prepended(base)
- super
-
- base.class_eval do
- view 'app/assets/javascripts/ide/components/error_message.vue' do
- element :flash_alert
- end
- end
- end
-
- def has_no_alert?(message = nil)
- return has_no_element?(:flash_alert) if message.nil?
-
- within_element(:flash_alert) do
- has_no_text?(message)
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/component/web_ide/modal/create_new_file.rb b/qa/qa/page/component/web_ide/modal/create_new_file.rb
deleted file mode 100644
index 22cac83d913..00000000000
--- a/qa/qa/page/component/web_ide/modal/create_new_file.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Component
- module WebIDE
- module Modal
- class CreateNewFile < Page::Base
- view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
- element 'file-name-field', required: true
- element :new_file_modal, required: true
- element :template_list_content
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/component/web_ide/web_terminal_panel.rb b/qa/qa/page/component/web_ide/web_terminal_panel.rb
deleted file mode 100644
index 80f83bcff7c..00000000000
--- a/qa/qa/page/component/web_ide/web_terminal_panel.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Component
- module WebIDE
- module WebTerminalPanel
- extend QA::Page::PageConcern
-
- def self.prepended(base)
- super
-
- base.class_eval do
- view 'app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue' do
- element :ide_right_sidebar, %q(:data-qa-selector="`ide_${side}_sidebar`") # rubocop:disable QA/ElementWithPattern
- end
-
- view 'app/assets/javascripts/ide/components/ide_sidebar_nav.vue' do
- element :terminal_tab_button, %q(:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`") # rubocop:disable QA/ElementWithPattern
- end
-
- view 'app/assets/javascripts/ide/components/terminal/empty_state.vue' do
- element :start_web_terminal_button
- end
-
- view 'app/assets/javascripts/ide/components/terminal/terminal.vue' do
- element :loading_container
- element :terminal_screen
- end
- end
- end
-
- def has_finished_loading?
- has_no_element?(:loading_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
- end
-
- def has_terminal_screen?
- wait_until(reload: false) do
- within_element :terminal_screen do
- # The DOM initially just includes the :terminal_screen element
- # and then the xterm package dynamically loads when the user
- # clicks the Start Web Terminal button. If it loads succesfully
- # an element with the class `xterm` is added to the DOM.
- # The xterm is a third-party library, so we can't add a selector
- find(".xterm")
- end
- end
- end
-
- def start_web_terminal
- within_element :ide_right_sidebar do
- click_element :terminal_tab_button
- end
-
- click_element :start_web_terminal_button
-
- has_element? :loading_container, text: "Starting"
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/component/wiki.rb b/qa/qa/page/component/wiki.rb
index d5daaf7d8ea..f90887a46bd 100644
--- a/qa/qa/page/component/wiki.rb
+++ b/qa/qa/page/component/wiki.rb
@@ -20,7 +20,6 @@ module QA
base.view 'app/views/shared/wikis/_main_links.html.haml' do
element 'new-page-button'
- element 'page-history-button'
end
base.view 'app/views/shared/empty_states/_wikis.html.haml' do
@@ -49,6 +48,7 @@ module QA
end
def click_page_history
+ click_element('wiki-more-dropdown')
click_element('page-history-button')
end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index ff822a6b117..32e3af117fc 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -25,9 +25,12 @@ module QA
element 'new-project-button'
end
- def has_project_with_access_role?(project_name, access_role)
- within_element('project-content', text: project_name) do
- has_element?('user-role-content', text: access_role)
+ def has_filtered_project_with_access_role?(project_name, access_role)
+ # Retry as in some situations the filter may fail if sidekiq hasn't had a chance
+ # to process all jobs after the project create
+ QA::Support::Retrier.retry_until(max_duration: 60, retry_on_exception: true) do
+ filter_by_name(project_name)
+ has_project_with_access_role?(project_name, access_role)
end
end
@@ -54,6 +57,14 @@ module QA
def clear_project_filter
fill_element('project-filter-form-container', "")
end
+
+ private
+
+ def has_project_with_access_role?(project_name, access_role)
+ within_element('project-content', text: project_name) do
+ has_element?('user-role-content', text: access_role)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index 30cd4f11bb4..61216f7b28d 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -15,7 +15,7 @@ module QA
end
view 'app/assets/javascripts/blob/filepath_form/components/template_selector.vue' do
- element :template_selector
+ element 'template-selector'
end
def add_name(name)
@@ -35,7 +35,7 @@ module QA
def select_template(template_type, template)
case template_type
when '.gitignore', '.gitlab-ci.yml', 'Dockerfile', 'LICENSE'
- click_element :template_selector
+ click_element 'template-selector'
else
raise %(Unsupported template_type "#{template_type}". Please confirm that it is a valid option.)
end
diff --git a/qa/qa/page/group/bulk_import.rb b/qa/qa/page/group/bulk_import.rb
index 52e5593cb26..6a86693591b 100644
--- a/qa/qa/page/group/bulk_import.rb
+++ b/qa/qa/page/group/bulk_import.rb
@@ -5,9 +5,8 @@ module QA
module Group
class BulkImport < Page::Base
view "app/assets/javascripts/import_entities/import_groups/components/import_table.vue" do
- element :import_table
- element :import_item
- element :import_status_indicator
+ element 'import-item'
+ element 'import-status-indicator'
element 'filter-groups'
end
@@ -35,14 +34,14 @@ module QA
filter_group(source_group_name)
- within_element(:import_item, source_group: source_group_name) do
+ within_element('import-item', source_group: source_group_name) do
click_element('target-namespace-dropdown')
click_element("listbox-item-#{target_group_name}")
retry_until(message: "Triggering import") do
click_element('import-group-button')
# Make sure import started before waiting for completion
- has_no_element?(:import_status_indicator, text: "Not started", wait: 1)
+ has_no_element?('import-status-indicator', text: "Not started", wait: 1)
end
end
end
@@ -53,8 +52,8 @@ module QA
# @param [Integer] wait
# @return [Boolean]
def has_imported_group?(source_group_name, wait: QA::Support::WaitForRequests::DEFAULT_MAX_WAIT_TIME)
- within_element(:import_item, source_group: source_group_name) do
- has_element?(:import_status_indicator, text: "Complete", wait: wait)
+ within_element('import-item', source_group: source_group_name) do
+ has_element?('import-status-indicator', text: "Complete", wait: wait)
end
end
end
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
index c12a9a60fa2..57e532be401 100644
--- a/qa/qa/page/group/settings/general.rb
+++ b/qa/qa/page/group/settings/general.rb
@@ -11,33 +11,33 @@ module QA
include Page::Component::NamespaceSelect
view 'app/views/groups/edit.html.haml' do
- element :permission_lfs_2fa_content
+ element 'permissions-settings'
element 'advanced-settings-content'
end
view 'app/views/groups/settings/_permissions.html.haml' do
- element :save_permissions_changes_button
+ element 'save-permissions-changes-button'
end
view 'app/views/groups/settings/_general.html.haml' do
- element :group_name_field
- element :save_name_visibility_settings_button
+ element 'group-name-field'
+ element 'save-name-visibility-settings-button'
end
view 'app/views/groups/settings/_lfs.html.haml' do
- element :lfs_checkbox
+ element 'lfs-checkbox'
end
view 'app/views/shared/_allow_request_access.html.haml' do
- element :request_access_checkbox
+ element 'request-access-checkbox'
end
view 'app/views/groups/settings/_two_factor_auth.html.haml' do
- element :require_2fa_checkbox
+ element 'require-2fa-checkbox'
end
view 'app/views/groups/settings/_project_creation_level.html.haml' do
- element :project_creation_level_dropdown
+ element 'project-creation-level-dropdown'
end
view 'app/views/groups/settings/_transfer.html.haml' do
@@ -49,66 +49,66 @@ module QA
end
def set_group_name(name)
- find_element(:group_name_field).send_keys([:command, 'a'], :backspace)
- find_element(:group_name_field).set name
+ find_element('group-name-field').send_keys([:command, 'a'], :backspace)
+ find_element('group-name-field').set name
end
def click_save_name_visibility_settings_button
- click_element(:save_name_visibility_settings_button)
+ click_element('save-name-visibility-settings-button')
end
def set_lfs_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:lfs_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('lfs-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_lfs_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:lfs_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('lfs-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_request_access_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:request_access_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('request-access-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_request_access_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:request_access_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('request-access-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_require_2fa_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:require_2fa_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('require-2fa-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_require_2fa_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:require_2fa_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('require-2fa-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_project_creation_level(value)
- expand_content(:permission_lfs_2fa_content)
- select_element(:project_creation_level_dropdown, value)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ select_element('project-creation-level-dropdown', value)
+ click_element('save-permissions-changes-button')
end
def toggle_request_access
- expand_content(:permission_lfs_2fa_content)
+ expand_content('permissions-settings')
- if find_element(:request_access_checkbox, visible: false).checked?
- uncheck_element(:request_access_checkbox, true)
+ if find_element('request-access-checkbox', visible: false).checked?
+ uncheck_element('request-access-checkbox', true)
else
- check_element(:request_access_checkbox, true)
+ check_element('request-access-checkbox', true)
end
- click_element(:save_permissions_changes_button)
+ click_element('save-permissions-changes-button')
end
def transfer_group(source_group, target_group)
diff --git a/qa/qa/page/group/settings/package_registries.rb b/qa/qa/page/group/settings/package_registries.rb
index bcf51f0f223..76f50809d83 100644
--- a/qa/qa/page/group/settings/package_registries.rb
+++ b/qa/qa/page/group/settings/package_registries.rb
@@ -7,23 +7,23 @@ module QA
include ::QA::Page::Settings::Common
view 'app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue' do
- element :package_registry_settings_content
- element :allow_duplicates_toggle
+ element 'package-registry-settings-content'
+ element 'maven-settings'
end
view 'app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue' do
- element :dependency_proxy_settings_content
- element :dependency_proxy_setting_toggle
+ element 'dependency-proxy-settings-content'
+ element 'dependency-proxy-setting-toggle'
end
def set_allow_duplicates_disabled
- within_element :package_registry_settings_content do
+ within_element 'package-registry-settings-content' do
click_on_allow_duplicates_button if duplicates_enabled?
end
end
def set_allow_duplicates_enabled
- within_element :package_registry_settings_content do
+ within_element 'package-registry-settings-content' do
click_on_allow_duplicates_button unless duplicates_enabled?
end
end
@@ -41,15 +41,15 @@ module QA
end
def with_allow_duplicates_button
- within_element :allow_duplicates_toggle do
+ within_element 'maven-settings' do
toggle = find('button.gl-toggle:not(.is-disabled)')
yield(toggle)
end
end
def has_dependency_proxy_enabled?
- within_element :dependency_proxy_settings_content do
- within_element :dependency_proxy_setting_toggle do
+ within_element 'dependency-proxy-settings-content' do
+ within_element 'dependency-proxy-setting-toggle' do
toggle = find('button.gl-toggle')
toggle[:class].include?('is-checked')
end
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 4dd0f3a5704..bf1cab510c8 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -9,13 +9,7 @@ module QA
view 'app/views/groups/_home_panel.html.haml' do
element 'new-project-button'
- element :new_subgroup_button
- element :group_id_content
- end
-
- view 'app/views/shared/members/_access_request_links.html.haml' do
- element :leave_group_link
- element 'request-access-link'
+ element 'new-subgroup-button'
end
def click_subgroup(name)
@@ -24,7 +18,7 @@ module QA
def has_new_project_and_new_subgroup_buttons?
has_element?('new_project_button')
- has_element?(:new_subgroup_button)
+ has_element?('new-subgroup-button')
end
def has_subgroup?(name)
@@ -32,7 +26,7 @@ module QA
end
def go_to_new_subgroup
- click_element :new_subgroup_button
+ click_element('new-subgroup-button')
end
def go_to_new_project
@@ -40,15 +34,21 @@ module QA
end
def group_id
- find_element(:group_id_content).text.delete('Group ID: ').sub(/\n.*/, '')
+ find_element('group-id-content').text.delete('Group ID: ').sub(/\n.*/, '')
end
def leave_group
- click_element :leave_group_link
+ click_element 'groups-projects-more-actions-dropdown'
+ wait_for_requests
+
+ click_element 'leave-group-link'
click_confirmation_ok_button
end
def click_request_access
+ click_element 'groups-projects-more-actions-dropdown'
+ wait_for_requests
+
click_element 'request-access-link'
end
end
diff --git a/qa/qa/page/layout/flash.rb b/qa/qa/page/layout/flash.rb
index 6bd3d6ab76b..d379ea1428e 100644
--- a/qa/qa/page/layout/flash.rb
+++ b/qa/qa/page/layout/flash.rb
@@ -10,12 +10,12 @@ module QA
super
base.view 'app/views/layouts/_flash.html.haml' do
- element :flash_container
+ element 'flash-container'
end
end
def has_notice?(message)
- within_element(:flash_container) do
+ within_element('flash-container') do
has_text?(message)
end
end
diff --git a/qa/qa/page/layout/performance_bar.rb b/qa/qa/page/layout/performance_bar.rb
index c593783a730..d590f425e1e 100644
--- a/qa/qa/page/layout/performance_bar.rb
+++ b/qa/qa/page/layout/performance_bar.rb
@@ -5,34 +5,34 @@ module QA
module Layout
class PerformanceBar < Page::Base
view 'app/assets/javascripts/performance_bar/components/performance_bar_app.vue' do
- element :performance_bar
+ element 'performance-bar'
end
view 'app/assets/javascripts/performance_bar/components/detailed_metric.vue' do
- element :detailed_metric_content
+ element 'detailed-metric-content'
end
view 'app/assets/javascripts/performance_bar/components/request_selector.vue' do
- element :request_dropdown_option
- element :request_dropdown
+ element 'request-dropdown-option'
+ element 'request-dropdown'
end
def has_performance_bar?
- has_element?(:performance_bar)
+ has_element?('performance-bar')
end
def has_detailed_metrics?(minimum_count)
retry_until(sleep_interval: 1) do
- all_elements(:detailed_metric_content, minimum: minimum_count).all? do |metric|
+ all_elements(:'detailed-metric-content', minimum: minimum_count).all? do |metric|
metric.has_text?(%r{\d+})
end
end
end
def has_request_for?(path)
- click_element(:request_dropdown)
+ click_element('request-dropdown')
retry_until(sleep_interval: 1) do
- has_element?(:request_dropdown_option, text: path)
+ has_element?('request-dropdown-option', text: path)
end
end
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 97cd9fc443a..028a6d37af4 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -8,55 +8,55 @@ module QA
include Runtime::Canary
view 'app/views/devise/passwords/edit.html.haml' do
- element :password_field
- element :password_confirmation_field
- element :change_password_button
+ element 'password-field'
+ element 'password-confirmation-field'
+ element 'change-password-button'
end
view 'app/views/devise/sessions/new.html.haml' do
- element :register_link
+ element 'register-link'
end
view 'app/views/devise/sessions/_new_base.html.haml' do
- element :login_field
- element :password_field
- element :sign_in_button
+ element 'username-field'
+ element 'password-field'
+ element 'sign-in-button'
end
view 'app/views/devise/sessions/_new_ldap.html.haml' do
- element :username_field
- element :password_field
- element :sign_in_button
+ element 'username-field'
+ element 'password-field'
+ element 'sign-in-button'
end
view 'app/views/devise/shared/_tabs_ldap.html.haml' do
- element :ldap_tab
- element :standard_tab
- element :register_tab
+ element 'ldap-tab'
+ element 'standard-tab'
+ element 'register-tab'
end
view 'app/views/devise/shared/_tab_single.html.haml' do
- element :sign_in_tab
+ element 'sign-in-tab'
end
view 'app/helpers/auth_helper.rb' do
- element :saml_login_button
- element :github_login_button
- element :oidc_login_button
- element :gitlab_oauth_login_button
- element :facebook_login_button
+ element 'saml-login-button'
+ element 'github-login-button'
+ element 'oidc-login-button'
+ element 'gitlab-oauth-login-button'
+ element 'facebook-login-button'
end
view 'app/views/layouts/devise.html.haml' do
- element :login_page, required: true
+ element 'login-page', required: true
end
def can_sign_in?
- has_element?(:sign_in_button)
+ has_element?('sign-in-button')
end
def on_login_page?
- has_element?(:login_page, wait: 0)
+ has_element?('login-page', wait: 0)
end
def sign_in_using_credentials(user: nil, skip_page_validation: false)
@@ -99,9 +99,9 @@ module QA
switch_to_ldap_tab
- fill_element :username_field, user.ldap_username
- fill_element :password_field, user.ldap_password
- click_element :sign_in_button
+ fill_element 'username-field', user.ldap_username
+ fill_element 'password-field', user.ldap_password
+ click_element 'sign-in-button'
end
Page::Main::Menu.perform(&:signed_in?)
@@ -131,15 +131,15 @@ module QA
end
def has_sign_in_tab?(wait: Capybara.default_max_wait_time)
- has_element?(:sign_in_tab, wait: wait)
+ has_element?('sign-in-tab', wait: wait)
end
def has_ldap_tab?
- has_element?(:ldap_tab)
+ has_element?('ldap-tab')
end
def has_standard_tab?
- has_element?(:standard_tab)
+ has_element?('standard-tab')
end
def sign_in_tab?
@@ -163,45 +163,45 @@ module QA
end
def switch_to_sign_in_tab
- click_element :sign_in_tab
+ click_element 'sign-in-tab'
end
def switch_to_register_page
set_initial_password_if_present
- click_element :register_link
+ click_element 'register-link'
end
def switch_to_ldap_tab
- click_element :ldap_tab
+ click_element 'ldap-tab'
end
def switch_to_standard_tab
- click_element :standard_tab
+ click_element 'standard-tab'
end
def sign_in_with_github
set_initial_password_if_present
- click_element :github_login_button
+ click_element 'github-login-button'
end
def sign_in_with_facebook
set_initial_password_if_present
- click_element :facebook_login_button
+ click_element 'facebook-login-button'
end
def sign_in_with_saml
set_initial_password_if_present
- click_element :saml_login_button
+ click_element 'saml-login-button'
end
def sign_in_with_gitlab_oidc
set_initial_password_if_present
- click_element :oidc_login_button
+ click_element 'oidc-login-button'
end
def sign_in_with_gitlab_oauth
set_initial_password_if_present
- click_element :gitlab_oauth_login_button
+ click_element 'gitlab-oauth-login-button'
end
def sign_out_and_sign_in_as(user:)
@@ -234,7 +234,7 @@ module QA
click_accept_all_cookies if Runtime::Env.running_on_dot_com? && has_accept_all_cookies_button?
- click_element :sign_in_button
+ click_element 'sign-in-button'
Support::WaitForRequests.wait_for_requests
@@ -259,16 +259,16 @@ module QA
end
def fill_in_credential(user)
- fill_element :login_field, user.username
- fill_element :password_field, user.password
+ fill_element 'username-field', user.username
+ fill_element 'password-field', user.password
end
def set_initial_password_if_present
return unless has_content?('Change your password')
- fill_element :password_field, Runtime::User.password
- fill_element :password_confirmation_field, Runtime::User.password
- click_element :change_password_button
+ fill_element 'password-field', Runtime::User.password
+ fill_element 'password-confirmation-field', Runtime::User.password
+ click_element 'change-password-button'
end
end
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 0a023c757ee..8fd3a8ca52f 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -6,12 +6,11 @@ module QA
class Menu < Page::Base
# We need to check phone_layout? instead of mobile_layout? here
# since tablets have the regular top navigation bar
- prepend Mobile::Page::Main::Menu if Runtime::Env.phone_layout?
include SubMenus::CreateNewMenu
include SubMenus::SuperSidebar::GlobalSearchModal
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
- element :navbar, required: true # TODO: rename to sidebar once it's default implementation
+ element 'super-sidebar', required: true
end
view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do
@@ -20,9 +19,9 @@ module QA
view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do
element 'user-dropdown', required: !Runtime::Env.phone_layout?
- element :user_avatar_content, required: !Runtime::Env.phone_layout?
- element :sign_out_link
- element :edit_profile_link
+ element 'user-avatar-content', required: !Runtime::Env.phone_layout?
+ element 'sign-out-link'
+ element 'edit-profile-link'
end
view 'app/assets/javascripts/super_sidebar/components/user_menu_profile_item.vue' do
@@ -36,49 +35,14 @@ module QA
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
end
- view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do
- element 'global-search-input'
- end
-
- view 'app/assets/javascripts/nav/components/top_nav_app.vue' do
- element :navbar_dropdown
- end
-
- view 'app/assets/javascripts/nav/components/top_nav_dropdown_menu.vue' do
- element 'menu-subview'
- end
-
view 'lib/gitlab/nav/top_nav_menu_item.rb' do
- element :menu_item_link
- end
-
- view 'app/helpers/nav/top_nav_helper.rb' do
- element :admin_area_link
- element :projects_dropdown
- element :groups_dropdown
- element :menu_item_link
- 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 'global-search-input'
- end
-
- view 'app/views/layouts/header/_new_dropdown.html.haml' do
- element 'new-menu-toggle'
+ element 'menu-item-link'
end
view 'app/helpers/nav/new_dropdown_helper.rb' do
- element :global_new_group_link
- element :global_new_project_link
- element :global_new_snippet_link
- end
-
- view 'app/assets/javascripts/nav/components/new_nav_toggle.vue' do
- element :new_navigation_toggle
+ element 'global-new-group-link'
+ element 'global-new-project-link'
+ element 'global-new-snippet-link'
end
def go_to_projects
@@ -122,7 +86,7 @@ module QA
has_element?('user-profile-link', text: /#{user.username}/)
end
# we need to close user menu because plain user link check will leave it open
- click_element :user_avatar_content if has_element?('user-profile-link', wait: 0)
+ click_element 'user-avatar-content' if has_element?('user-profile-link', wait: 0)
end
def not_signed_in?
@@ -140,7 +104,7 @@ module QA
break true unless signed_in?
within_user_menu do
- click_element :sign_out_link
+ click_element 'sign-out-link'
end
not_signed_in?
@@ -154,7 +118,7 @@ module QA
def click_edit_profile_link
retry_until(reload: false) do
within_user_menu do
- click_element(:edit_profile_link)
+ click_element('edit-profile-link')
end
has_text?('User Settings')
@@ -168,11 +132,11 @@ module QA
end
def has_personal_area?(wait: Capybara.default_max_wait_time)
- has_element?(:user_avatar_content, wait: wait)
+ has_element?('user-avatar-content', wait: wait)
end
def has_no_personal_area?(wait: Capybara.default_max_wait_time)
- has_no_element?(:user_avatar_content, wait: wait)
+ has_no_element?('user-avatar-content', wait: wait)
end
def click_stop_impersonation_link
@@ -192,8 +156,8 @@ module QA
private
def within_user_menu(&block)
- within_element(:navbar) do
- click_element :user_avatar_content unless has_element?('user-profile-link', wait: 1)
+ within_element('super-sidebar') do
+ click_element 'user-avatar-content' unless has_element?('user-profile-link', wait: 1)
within_element('user-dropdown', &block)
end
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index 2b1a9ab2b6a..410ffc7ff18 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -5,7 +5,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
- element :authorization_button
+ element 'authorization-button'
end
def needs_authorization?
@@ -13,7 +13,7 @@ module QA
end
def authorize!
- click_element :authorization_button
+ click_element 'authorization-button'
end
end
end
diff --git a/qa/qa/page/main/terms.rb b/qa/qa/page/main/terms.rb
index 24f6b03549b..1443c5b56f3 100644
--- a/qa/qa/page/main/terms.rb
+++ b/qa/qa/page/main/terms.rb
@@ -5,17 +5,17 @@ module QA
module Main
class Terms < Page::Base
view 'app/views/layouts/terms.html.haml' do
- element :user_avatar_content, required: true
+ element 'user-avatar-content', required: true
end
view 'app/assets/javascripts/terms/components/app.vue' do
- element :terms_content, required: true
+ element 'terms-content', required: true
- element :accept_terms_button
+ element 'accept-terms-button'
end
def accept_terms
- click_element :accept_terms_button, Page::Main::Menu
+ click_element 'accept-terms-button', Page::Main::Menu
end
end
end
diff --git a/qa/qa/page/main/two_factor_auth.rb b/qa/qa/page/main/two_factor_auth.rb
index 003bd8dd1b1..186027900ca 100644
--- a/qa/qa/page/main/two_factor_auth.rb
+++ b/qa/qa/page/main/two_factor_auth.rb
@@ -5,16 +5,16 @@ module QA
module Main
class TwoFactorAuth < Page::Base
view 'app/views/devise/sessions/two_factor.html.haml' do
- element :verify_code_button
- element :two_fa_code_field
+ element 'verify-code-button'
+ element 'two-fa-code-field'
end
def click_verify_code_button
- click_element :verify_code_button
+ click_element 'verify-code-button'
end
def set_2fa_code(code)
- fill_element(:two_fa_code_field, code)
+ fill_element('two-fa-code-field', code)
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 8129567c079..6110d33c015 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -66,10 +66,6 @@ module QA
element 'pipeline-id'
end
- view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue' do
- element 'merge-request-failed-refresh-button'
- end
-
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
element 'cherry-pick-button'
element 'revert-button'
@@ -228,6 +224,24 @@ module QA
has_element?('rebase-message')
end
+ def merge_blocked_component_ff_enabled?
+ element = within_element('.mr-widget-section') do
+ feature_flag_controlled_element(
+ :merge_blocked_component,
+ 'chevron-lg-down-icon',
+ 'standard-rebase-button'
+ )
+ end
+
+ !(element == 'standard-rebase-button')
+ end
+
+ def expand_merge_checks
+ within_element('.mr-widget-section') do
+ click_element('chevron-lg-down-icon')
+ end
+ end
+
def has_file?(file_name)
open_file_tree
@@ -254,14 +268,10 @@ module QA
end
def has_merge_button?
- refresh
-
- has_element?('merge-button')
+ has_element?('merge-button', wait: 30)
end
def has_no_merge_button?
- refresh
-
has_no_element?('merge-button')
end
@@ -419,12 +429,6 @@ module QA
visit_link_in_element('download-plain-diff-menu-item')
end
- def wait_for_merge_request_error_message
- wait_until(max_duration: 30, reload: false) do
- has_element?('merge-request-failed-refresh-button')
- end
- end
-
def click_open_in_web_ide
# Click by JS is needed to bypass the Moved MR actions popover
# Change back to regular click_element when moved_mr_sidebar FF is removed
diff --git a/qa/qa/page/modal/delete_wiki.rb b/qa/qa/page/modal/delete_wiki.rb
index 4f0bc34ee88..e051fc27e5e 100644
--- a/qa/qa/page/modal/delete_wiki.rb
+++ b/qa/qa/page/modal/delete_wiki.rb
@@ -5,11 +5,11 @@ module QA
module Modal
class DeleteWiki < Base
view 'app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue' do
- element :confirm_deletion_button, required: true
+ element 'confirm-deletion-button', required: true
end
def confirm_deletion
- click_element :confirm_deletion_button
+ click_element 'confirm-deletion-button'
end
end
end
diff --git a/qa/qa/page/profile/accounts/show.rb b/qa/qa/page/profile/accounts/show.rb
index 84a34d1da78..b29c1d9da12 100644
--- a/qa/qa/page/profile/accounts/show.rb
+++ b/qa/qa/page/profile/accounts/show.rb
@@ -6,24 +6,24 @@ module QA
module Accounts
class Show < Page::Base
view 'app/views/profiles/accounts/show.html.haml' do
- element :delete_account_button, required: true
- element :enable_2fa_button
+ element 'delete-account-button', required: true
+ element 'enable-2fa-button'
end
view 'app/assets/javascripts/profile/account/components/delete_account_modal.vue' do
- element :password_confirmation_field
- element :confirm_delete_account_button
+ element 'password-confirmation-field'
+ element 'confirm-delete-account-button'
end
def click_enable_2fa_button
- click_element(:enable_2fa_button)
+ click_element('enable-2fa-button')
end
def delete_account(password)
- click_element(:delete_account_button)
+ click_element('delete-account-button')
- find_element(:password_confirmation_field).set password
- click_element(:confirm_delete_account_button)
+ find_element('password-confirmation-field').set password
+ click_element('confirm-delete-account-button')
end
end
end
diff --git a/qa/qa/page/profile/password.rb b/qa/qa/page/profile/password.rb
index ee042450f8d..5f07cdb6f25 100644
--- a/qa/qa/page/profile/password.rb
+++ b/qa/qa/page/profile/password.rb
@@ -4,14 +4,14 @@ module QA
module Page
module Profile
class Password < Page::Base
- view 'app/views/profiles/passwords/edit.html.haml' do
+ view 'app/views/user_settings/passwords/edit.html.haml' do
element :current_password_field
element :new_password_field
element :confirm_password_field
element :save_password_button
end
- view 'app/views/profiles/passwords/new.html.haml' do
+ view 'app/views/user_settings/passwords/new.html.haml' do
element :current_password_field
element :new_password_field
element :confirm_password_field
diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb
index 2990ba6a4ac..b26b86e3713 100644
--- a/qa/qa/page/profile/ssh_keys.rb
+++ b/qa/qa/page/profile/ssh_keys.rb
@@ -5,35 +5,35 @@ module QA
module Profile
class SSHKeys < Page::Base
view 'app/views/profiles/keys/_form.html.haml' do
- element :key_title_field
- element :key_public_key_field
- element :add_key_button
+ element 'key-title-field'
+ element 'key-public-key-field'
+ element 'add-key-button'
end
view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do
- element :expiry_date_field
+ element 'expiry-date-field'
end
view 'app/helpers/ssh_keys_helper.rb' do
- element :delete_ssh_key_button
- element :ssh_key_delete_modal
+ element 'delete-ssh-key-button'
+ element 'ssh-key-delete-modal'
end
view 'app/views/profiles/keys/_key_table.html.haml' do
- element :ssh_keys_list
+ element 'ssh-keys-list'
end
def add_key(public_key, title)
click_button('Add new key')
- fill_element(:key_public_key_field, public_key)
- fill_element(:key_title_field, title)
+ fill_element('key-public-key-field', public_key)
+ fill_element('key-title-field', title)
# Expire in 2 days just in case the key is created just before midnight
fill_expiry_date(Date.today + 2)
# Close the datepicker
- find_element(:expiry_date_field).find('input').send_keys(:enter)
+ find_element('expiry-date-field').find('input').send_keys(:enter)
- click_element(:add_key_button)
+ click_element('add-key-button')
end
def fill_expiry_date(date)
@@ -44,25 +44,25 @@ module QA
raise "Expiry date must be in YYYY-MM-DD format"
end
- fill_element(:expiry_date_field, date)
+ fill_element('expiry-date-field', date)
end
def remove_key(title)
click_link(title)
- click_element(:delete_ssh_key_button)
+ click_element('delete-ssh-key-button')
# Retrying due to https://gitlab.com/gitlab-org/gitlab/-/issues/255287
retry_on_exception do
- wait_for_animated_element(:ssh_key_delete_modal)
- within_element(:ssh_key_delete_modal) do
+ wait_for_animated_element('ssh-key-delete-modal')
+ within_element('ssh-key-delete-modal') do
click_button('Delete')
end
end
end
def keys_list
- find_element(:ssh_keys_list).text
+ find_element('ssh-keys-list').text
end
end
end
diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb
index 2add02b5c48..980d4a74a8c 100644
--- a/qa/qa/page/profile/two_factor_auth.rb
+++ b/qa/qa/page/profile/two_factor_auth.rb
@@ -5,61 +5,61 @@ module QA
module Profile
class TwoFactorAuth < Page::Base
view 'app/assets/javascripts/pages/profiles/two_factor_auths/index.js' do
- element :configure_it_later_button
+ element 'configure-it-later-button'
end
view 'app/views/profiles/two_factor_auths/show.html.haml' do
- element :otp_secret_content
- element :pin_code_field
- element :current_password_field
- element :register_2fa_app_button
+ element 'otp-secret-content'
+ element 'pin-code-field'
+ element 'current-password-field'
+ element 'register-2fa-app-button'
end
view 'app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue' do
- element :proceed_button
- element :copy_button
- element :codes_content
- element :code_content
+ element 'proceed-button'
+ element 'copy-button'
+ element 'recovery-codes'
+ element 'code-content'
end
def click_configure_it_later_button
# TO DO: Investigate why button does not appear sometimes:
# https://gitlab.com/gitlab-org/gitlab/-/issues/382698
page.refresh
- return unless has_element?(:configure_it_later_button, wait: 60)
+ return unless has_element?('configure-it-later-button', wait: 60)
- click_element :configure_it_later_button
+ click_element 'configure-it-later-button'
wait_until(max_duration: 10, message: "Waiting for create a group page") do
has_text?("Welcome to GitLab") && has_text?("Create a group")
end
end
def otp_secret_content
- find_element(:otp_secret_content).text.gsub('Key:', '').delete(' ')
+ find_element('otp-secret-content').text.gsub('Key:', '').delete(' ')
end
def set_pin_code(pin_code)
- fill_element(:pin_code_field, pin_code)
+ fill_element('pin-code-field', pin_code)
end
def set_current_password(password)
- fill_element(:current_password_field, password)
+ fill_element('current-password-field', password)
end
def click_register_2fa_app_button
- click_element :register_2fa_app_button
+ click_element 'register-2fa-app-button'
end
def recovery_codes
- code_elements = within_element(:codes_content) do
- all_elements(:code_content, minimum: 1)
+ code_elements = within_element('recovery-codes') do
+ all_elements('code-content', minimum: 1)
end
code_elements.map { |code_content| code_content.text }
end
def click_copy_and_proceed
- click_element :copy_button
- click_element :proceed_button
+ click_element 'copy-button'
+ click_element 'proceed-button'
end
end
end
diff --git a/qa/qa/page/project/deployments/environments/index.rb b/qa/qa/page/project/deployments/environments/index.rb
deleted file mode 100644
index 598e1f26815..00000000000
--- a/qa/qa/page/project/deployments/environments/index.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Deployments
- module Environments
- class Index < Page::Base
- view 'app/assets/javascripts/environments/components/environment_item.vue' do
- element :environment_link
- end
-
- def click_environment_link(environment_name)
- click_element(:environment_link, text: environment_name)
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb
index 2b36766d996..9118aefc36c 100644
--- a/qa/qa/page/project/fork/new.rb
+++ b/qa/qa/page/project/fork/new.rb
@@ -8,28 +8,28 @@ module QA
include ::QA::Page::Component::Dropdown
view 'app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue' do
- element :fork_project_button
- element :fork_privacy_button
+ element 'fork-project-button'
+ element 'fork-privacy-button'
end
view 'app/assets/javascripts/pages/projects/forks/new/components/project_namespace.vue' do
- element :select_namespace_dropdown
+ element 'select-namespace-dropdown'
end
def fork_project(namespace = Runtime::Namespace.path)
choose_namespace(namespace)
- click_element(:fork_privacy_button, privacy_level: 'public')
- click_element(:fork_project_button)
+ click_element('fork-privacy-button', privacy_level: 'public')
+ click_element('fork-project-button')
end
def get_list_of_namespaces
- click_element(:select_namespace_dropdown)
+ click_element('select-namespace-dropdown')
all_items
end
def choose_namespace(namespace)
retry_on_exception do
- click_element(:select_namespace_dropdown)
+ click_element('select-namespace-dropdown')
search_and_select(namespace)
end
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 5d69909a323..303bb1ae2b6 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -4,7 +4,6 @@ module QA
module Page
module Project
class Menu < Page::Base
- include SubMenus::Common
include SubMenus::CreateNewMenu
include SubMenus::Plan
include SubMenus::Settings
diff --git a/qa/qa/page/project/monitor/alerts/show.rb b/qa/qa/page/project/monitor/alerts/show.rb
index 1f3c52d8988..b1866c6d366 100644
--- a/qa/qa/page/project/monitor/alerts/show.rb
+++ b/qa/qa/page/project/monitor/alerts/show.rb
@@ -7,7 +7,7 @@ module QA
module Alerts
class Show < Page::Base
view 'app/assets/javascripts/vue_shared/alert_details/components/system_notes/system_note.vue' do
- element :alert_system_note_container
+ element 'alert-system-note-container'
end
def go_to_activity_feed_tab
@@ -15,7 +15,7 @@ module QA
end
def has_system_note?(text)
- has_element?(:alert_system_note_container, text: text)
+ has_element?('alert-system-note-container', text: text)
end
end
end
diff --git a/qa/qa/page/project/pages.rb b/qa/qa/page/project/pages.rb
new file mode 100644
index 00000000000..1a7a8a1e93c
--- /dev/null
+++ b/qa/qa/page/project/pages.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ class Pages < Page::Base
+ view 'app/views/projects/pages/_access.html.haml' do
+ element 'access-page-container'
+ end
+
+ def go_to_access_page
+ within_element('access-page-container') do
+ find('a').click
+ page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
+ end
+ end
+ end
+ end
+ end
+end
+
+QA::Page::Project::Pages.prepend_mod_with("Page::Project::Pages", namespace: QA)
diff --git a/qa/qa/page/project/settings/auto_devops.rb b/qa/qa/page/project/settings/auto_devops.rb
index 9dffa010805..e057d778679 100644
--- a/qa/qa/page/project/settings/auto_devops.rb
+++ b/qa/qa/page/project/settings/auto_devops.rb
@@ -6,13 +6,13 @@ module QA
module Settings
class AutoDevops < Page::Base
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
- element :enable_autodevops_checkbox
- element :save_changes_button
+ element 'enable-autodevops-checkbox'
+ element 'save-changes-button'
end
def enable_autodevops
- check_element(:enable_autodevops_checkbox)
- click_element(:save_changes_button)
+ check_element('enable-autodevops-checkbox')
+ click_element('save-changes-button')
end
end
end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index c5fad2efcfe..35188dad2ba 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -8,25 +8,25 @@ module QA
include QA::Page::Settings::Common
view 'app/views/projects/settings/ci_cd/show.html.haml' do
- element :autodevops_settings_content
- element :runners_settings_content
- element :variables_settings_content
+ element 'autodevops-settings-content'
+ element 'runners-settings-content'
+ element 'variables-settings-content'
end
def expand_runners_settings(&block)
- expand_content(:runners_settings_content) do
+ expand_content('runners-settings-content') do
Settings::Runners.perform(&block)
end
end
def expand_ci_variables(&block)
- expand_content(:variables_settings_content) do
+ expand_content('variables-settings-content') do
Settings::CiVariables.perform(&block)
end
end
def expand_auto_devops(&block)
- expand_content(:autodevops_settings_content) do
+ expand_content('autodevops-settings-content') do
Settings::AutoDevops.perform(&block)
end
end
diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb
index c97593312d1..1543170f0cc 100644
--- a/qa/qa/page/project/settings/integrations.rb
+++ b/qa/qa/page/project/settings/integrations.rb
@@ -6,31 +6,31 @@ module QA
module Settings
class Integrations < QA::Page::Base
view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
- element :jenkins_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
- element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
- element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
- element :pipelines_email_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
- element :gitlab_slack_application_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
+ element 'jenkins-link', %q(:data-testid="`${item.name}-link`") # rubocop:disable QA/ElementWithPattern -- required for qa:selectors job to pass
+ element 'prometheus-link', %q(:data-testid="`${item.name}-link`") # rubocop:disable QA/ElementWithPattern -- required for qa:selectors job to pass
+ element 'jira-link', %q(:data-testid="`${item.name}-link`") # rubocop:disable QA/ElementWithPattern -- required for qa:selectors job to pass
+ element 'pipelines_email-link', %q(:data-testid="`${item.name}-link`") # rubocop:disable QA/ElementWithPattern -- required for qa:selectors job to pass
+ element 'gitlab_slack_application-link', %q(:data-testid="`${item.name}-link`") # rubocop:disable QA/ElementWithPattern -- required for qa:selectors job to pass
end
def click_on_prometheus_integration
- click_element :prometheus_link
+ click_element('prometheus-link')
end
def click_pipelines_email_link
- click_element :pipelines_email_link
+ click_element('pipelines_email-link')
end
def click_jira_link
- click_element :jira_link
+ click_element('jira-link')
end
def click_jenkins_ci_link
- click_element :jenkins_link
+ click_element('jenkins-link')
end
def click_slack_application_link
- click_element :gitlab_slack_application_link
+ click_element('gitlab_slack_application-link')
end
end
end
diff --git a/qa/qa/page/project/settings/pages.rb b/qa/qa/page/project/settings/pages.rb
deleted file mode 100644
index 64990dc2991..00000000000
--- a/qa/qa/page/project/settings/pages.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module Settings
- class Pages < Page::Base
- include QA::Page::Settings::Common
-
- view 'app/views/projects/pages/_access.html.haml' do
- element 'access-page-container'
- end
-
- def go_to_access_page
- within_element('access-page-container') do
- find('a').click
- page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
- end
- end
- end
- end
- end
- end
-end
-
-QA::Page::Project::Settings::Pages.prepend_mod_with("Page::Project::Settings::Pages", namespace: QA)
diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb
index aa1ac216ae2..ce3bc1a5396 100644
--- a/qa/qa/page/project/settings/runners.rb
+++ b/qa/qa/page/project/settings/runners.rb
@@ -5,27 +5,12 @@ module QA
module Project
module Settings
class Runners < Page::Base
- view 'app/views/ci/runner/_how_to_setup_runner.html.haml' do
- element :registration_token, '%code#registration_token' # rubocop:disable QA/ElementWithPattern
- element :coordinator_address, '%code#coordinator_address' # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/helpers/ci/runners_helper.rb' do
- # rubocop:disable Lint/InterpolationCheck
- element :runner_status_icon, 'qa_selector: "runner_status_#{status}_content"' # rubocop:disable QA/ElementWithPattern
- # rubocop:enable Lint/InterpolationCheck
- end
-
- def registration_token
- find('code#registration_token').text
- end
-
- def coordinator_address
- find('code#coordinator_address').text
+ element 'runner-status-icon'
end
def has_online_runner?
- has_element?(:runner_status_online_content)
+ has_element?('runner-status-icon', status: 'online')
end
end
end
diff --git a/qa/qa/page/project/settings/services/jenkins.rb b/qa/qa/page/project/settings/services/jenkins.rb
index a9b5c84f9ee..b5d08c646f8 100644
--- a/qa/qa/page/project/settings/services/jenkins.rb
+++ b/qa/qa/page/project/settings/services/jenkins.rb
@@ -7,14 +7,14 @@ module QA
module Services
class Jenkins < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
- 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
+ element 'service-jenkins_url-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'service-project_name-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'service-username-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'service-password-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
end
view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do
- element :save_changes_button
+ element 'save-changes-button'
end
def setup_service_with(jenkins_url:, project_name:, username:, password:)
@@ -28,23 +28,23 @@ module QA
private
def set_jenkins_url(jenkins_url)
- fill_element(:service_jenkins_url_field, jenkins_url)
+ fill_element('service-jenkins_url-field', jenkins_url)
end
def set_project_name(project_name)
- fill_element(:service_project_name_field, project_name)
+ fill_element('service-project_name-field', project_name)
end
def set_username(username)
- fill_element(:service_username_field, username)
+ fill_element('service-username-field', username)
end
def set_password(password)
- fill_element(:service_password_field, password)
+ fill_element('service-password-field', password)
end
def click_save_changes_button
- click_element :save_changes_button
+ click_element 'save-changes-button'
end
end
end
diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb
index 7a62b111f98..681f545f1bf 100644
--- a/qa/qa/page/project/settings/services/jira.rb
+++ b/qa/qa/page/project/settings/services/jira.rb
@@ -7,25 +7,25 @@ module QA
module Services
class Jira < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/dynamic_field.vue' do
- element :service_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
- element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
- element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern
+ element 'service-url-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'service-username-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'service-password-field', ':data-testid="`${fieldId}-field`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
end
view 'app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue' do
- element :service_jira_issue_transition_enabled_checkbox
- element :service_jira_issue_transition_automatic_true_radio, ':data-qa-selector="`service_jira_issue_transition_automatic_${issueTransitionOption.value}_radio`"' # rubocop:disable QA/ElementWithPattern
- element :service_jira_issue_transition_automatic_false_radio, ':data-qa-selector="`service_jira_issue_transition_automatic_${issueTransitionOption.value}_radio`"' # rubocop:disable QA/ElementWithPattern
- element :service_jira_issue_transition_id_field
+ element 'jira-issue-transition-enabled-checkbox'
+ element 'jira-issue-transition-automatic-true-radio', ':data-testid="`jira-issue-transition-automatic-${issueTransitionOption.value}-radio`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'jira-issue-transition-automatic-false-radio', ':data-testid="`jira-issue-transition-automatic-${issueTransitionOption.value}-radio`"' # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'jira-issue-transition-id-field'
end
view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do
- element :save_changes_button
+ element 'save-changes-button'
end
view 'app/assets/javascripts/integrations/edit/components/jira_issues_fields.vue' do
- element :service_jira_issues_enabled_checkbox
- element :service_jira_project_key_field
+ element 'jira-issues-enabled-checkbox'
+ element 'jira-project-key-field'
end
def setup_service_with(url:)
@@ -45,52 +45,52 @@ module QA
end
def enable_jira_issues
- check_element(:service_jira_issues_enabled_checkbox, true)
+ check_element('jira-issues-enabled-checkbox', true)
end
def set_jira_project_key(key)
- fill_element(:service_jira_project_key_field, key)
+ fill_element('jira-project-key-field', key)
end
def click_save_changes_and_wait
click_save_changes_button
wait_until(reload: false) do
- has_element?(:save_changes_button, wait: 1) ? !find_element(:save_changes_button).disabled? : true
+ has_element?('save-changes-button', wait: 1) ? !find_element('save-changes-button').disabled? : true
end
end
private
def set_jira_server_url(url)
- fill_element(:service_url_field, url)
+ fill_element('service-url-field', url)
end
def set_username(username)
- fill_element(:service_username_field, username)
+ fill_element('service-username-field', username)
end
def set_password(password)
- fill_element(:service_password_field, password)
+ fill_element('service-password-field', password)
end
def enable_transitions
- check_element(:service_jira_issue_transition_enabled_checkbox, true)
+ check_element('jira-issue-transition-enabled-checkbox', true)
end
def use_automatic_transitions
- choose_element(:service_jira_issue_transition_automatic_true_radio, true)
+ choose_element('jira-issue-transition-automatic-true-radio', true)
end
def use_custom_transitions
- choose_element(:service_jira_issue_transition_automatic_false_radio, true)
+ choose_element('jira-issue-transition-automatic-false-radio', true)
end
def set_transition_ids(transition_ids)
- fill_element(:service_jira_issue_transition_id_field, transition_ids)
+ fill_element('jira-issue-transition-id-field', transition_ids)
end
def click_save_changes_button
- click_element(:save_changes_button)
+ click_element('save-changes-button')
end
end
end
diff --git a/qa/qa/page/project/settings/services/pipeline_status_emails.rb b/qa/qa/page/project/settings/services/pipeline_status_emails.rb
index 3edd1d61d76..548a1795126 100644
--- a/qa/qa/page/project/settings/services/pipeline_status_emails.rb
+++ b/qa/qa/page/project/settings/services/pipeline_status_emails.rb
@@ -7,28 +7,28 @@ module QA
module Services
class PipelineStatusEmails < QA::Page::Base
view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do
- element :recipients_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern
- element :notify_only_broken_pipelines_div, %q(:data-qa-selector="`${field.name}_div`") # rubocop:disable QA/ElementWithPattern
+ element 'recipients-div', %q(:data-testid="`${field.name}-div`") # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
+ element 'notify_only_broken_pipelines-div', %q(:data-testid="`${field.name}-div`") # rubocop:disable QA/ElementWithPattern -- needed for qa:selectors job to pass
end
view 'app/assets/javascripts/integrations/edit/components/integration_form_actions.vue' do
- element :save_changes_button
+ element 'save-changes-button'
end
def set_recipients(emails)
- within_element :recipients_div do
+ within_element 'recipients-div' do
fill_in 'Recipients', with: emails.join(',')
end
end
def toggle_notify_broken_pipelines
- within_element :notify_only_broken_pipelines_div do
+ within_element 'notify_only_broken_pipelines-div' do
uncheck 'Notify only broken pipelines', allow_label_click: true
end
end
def click_save_button
- click_element(:save_changes_button)
+ click_element('save-changes-button')
end
end
end
diff --git a/qa/qa/page/project/settings/visibility_features_permissions.rb b/qa/qa/page/project/settings/visibility_features_permissions.rb
index 60cea6de7f5..a0b21fd1698 100644
--- a/qa/qa/page/project/settings/visibility_features_permissions.rb
+++ b/qa/qa/page/project/settings/visibility_features_permissions.rb
@@ -6,13 +6,13 @@ module QA
module Settings
class VisibilityFeaturesPermissions < Page::Base
view 'app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue' do
- element :project_visibility_dropdown
- element :visibility_features_permissions_save_button
+ element 'project-visibility-dropdown'
+ element 'project-features-save-button'
end
def set_project_visibility(visibility)
- select_element(:project_visibility_dropdown, visibility)
- click_element :visibility_features_permissions_save_button
+ select_element('project-visibility-dropdown', visibility)
+ click_element 'project-features-save-button'
end
end
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 3223b2b488d..36c14054188 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -9,9 +9,6 @@ module QA
include Page::Component::Breadcrumbs
include Page::File::Shared::CommitMessage
include Page::Component::Dropdown
- # We need to check phone_layout? instead of mobile_layout? here
- # since tablets have the regular top navigation bar
- prepend Mobile::Page::Project::Show if Runtime::Env.phone_layout?
view 'app/assets/javascripts/repository/components/preview/index.vue' do
element 'blob-viewer-content'
@@ -25,17 +22,15 @@ module QA
element 'file-tree-table'
end
- view 'app/views/layouts/header/_new_dropdown.html.haml' do
- element 'new-menu-toggle'
- end
-
view 'app/views/projects/_last_push.html.haml' do
element 'create-merge-request-button'
end
view 'app/views/projects/_home_panel.html.haml' do
element 'project-name-content'
- element 'project-id-content'
+ end
+
+ view 'app/views/projects/_sidebar.html.haml' do
element 'project-badges-content'
element 'badge-image-link'
end
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
deleted file mode 100644
index 0619afc313c..00000000000
--- a/qa/qa/page/project/sub_menus/common.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module SubMenus
- module Common
- extend QA::Page::PageConcern
-
- def self.included(base)
- super
-
- base.class_eval do
- include QA::Page::SubMenus::Common
-
- view 'app/views/layouts/nav/_top_bar.html.haml' do
- element 'toggle-mobile-nav-button'
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index bf6b25062f0..76cc5ab7664 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -19,10 +19,6 @@ module QA
open_settings_submenu('Merge requests')
end
- def go_to_pages_settings
- open_settings_submenu('Pages')
- end
-
def go_to_monitor_settings
open_settings_submenu('Monitor')
end
diff --git a/qa/qa/page/project/tag/index.rb b/qa/qa/page/project/tag/index.rb
index b8f7bd3b0b4..5b5f3e8cc75 100644
--- a/qa/qa/page/project/tag/index.rb
+++ b/qa/qa/page/project/tag/index.rb
@@ -6,11 +6,11 @@ module QA
module Tag
class Index < Page::Base
view 'app/views/projects/tags/index.html.haml' do
- element :new_tag_button
+ element 'new-tag-button'
end
def click_new_tag_button
- click_element :new_tag_button
+ click_element('new-tag-button')
end
end
end
diff --git a/qa/qa/page/project/tag/new.rb b/qa/qa/page/project/tag/new.rb
index 50e11acd94a..53b31c9cbe5 100644
--- a/qa/qa/page/project/tag/new.rb
+++ b/qa/qa/page/project/tag/new.rb
@@ -6,21 +6,21 @@ module QA
module Tag
class New < Page::Base
view 'app/views/projects/tags/new.html.haml' do
- element :tag_name_field
- element :tag_message_field
- element :create_tag_button
+ element 'tag-name-field'
+ element 'tag-message-field'
+ element 'create-tag-button'
end
def fill_tag_name(text)
- fill_element(:tag_name_field, text)
+ fill_element('tag-name-field', text)
end
def fill_tag_message(text)
- fill_element(:tag_message_field, text)
+ fill_element('tag-message-field', text)
end
def click_create_tag_button
- click_element :create_tag_button
+ click_element 'create-tag-button'
end
end
end
diff --git a/qa/qa/page/project/tag/show.rb b/qa/qa/page/project/tag/show.rb
index b9703f46a50..a6afac574f5 100644
--- a/qa/qa/page/project/tag/show.rb
+++ b/qa/qa/page/project/tag/show.rb
@@ -6,16 +6,16 @@ module QA
module Tag
class Show < Page::Base
view 'app/views/projects/tags/show.html.haml' do
- element :tag_name_content
- element :tag_message_content
+ element 'tag-name-content'
+ element 'tag-message-content'
end
def has_tag_name?(text)
- has_element?(:tag_name_content, text: text)
+ has_element?('tag-name-content', text: text)
end
def has_tag_message?(text)
- has_element?(:tag_message_content, text: text)
+ has_element?('tag-message-content', text: text)
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
deleted file mode 100644
index a835ba9e85f..00000000000
--- a/qa/qa/page/project/web_ide/edit.rb
+++ /dev/null
@@ -1,349 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Page
- module Project
- module WebIDE
- class Edit < Page::Base
- prepend Page::Component::WebIDE::Alert
- prepend Page::Component::WebIDE::WebTerminalPanel
- include Page::Component::DropdownFilter
-
- view 'app/assets/javascripts/ide/components/activity_bar.vue' do
- element :commit_mode_tab
- element :edit_mode_tab
- end
-
- view 'app/assets/javascripts/ide/components/ide_status_bar.vue' do
- element :commit_sha_content
- end
-
- view 'app/assets/javascripts/ide/components/ide_tree.vue' do
- element :new_file_button, required: true
- element :new_directory_button, required: true
- end
-
- view 'app/assets/javascripts/ide/components/ide_tree_list.vue' do
- element :file_list_container
- end
-
- view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do
- element :file_templates_container
- element :file_template_dropdown
- end
-
- view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do
- element :dropdown_filter_input
- end
-
- view 'app/assets/javascripts/ide/components/commit_sidebar/actions.vue' do
- element :commit_to_current_branch_radio_container
- end
-
- view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
- element :begin_commit_button
- element :commit_button
- end
-
- view 'app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue' do
- element :commit_type_radio
- end
-
- view 'app/assets/javascripts/ide/components/repo_editor.vue' do
- element :editor_container
- end
-
- view 'app/assets/javascripts/ide/components/ide.vue' do
- element :first_file_button
- end
-
- view 'app/assets/javascripts/vue_shared/components/file_row.vue' do
- element 'file-row-name-container'
- element :file_row_container
- end
-
- view 'app/assets/javascripts/ide/components/new_dropdown/index.vue' do
- element :dropdown_button
- element :rename_move_button
- element :delete_button
- end
-
- view 'app/assets/javascripts/vue_shared/components/web_ide/confirm_fork_modal.vue' do
- element :fork_project_button
- element :confirm_fork_modal
- end
-
- view 'app/assets/javascripts/ide/components/ide_project_header.vue' do
- element :project_path_content
- end
-
- view 'app/assets/javascripts/ide/components/commit_sidebar/message_field.vue' do
- element :ide_commit_message_field
- end
-
- view 'app/assets/javascripts/vue_shared/components/changed_file_icon.vue' do
- element :changed_file_icon_content
- end
-
- view 'app/assets/javascripts/vue_shared/components/file_icon.vue' do
- element :folder_icon_content
- end
-
- view 'app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue' do
- element :preview_container
- end
-
- view 'app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue' do
- element :download_button
- end
-
- view 'app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue' do
- element :image_viewer_container
- end
-
- view 'app/assets/javascripts/ide/components/new_dropdown/upload.vue' do
- element :file_upload_field
- end
-
- view 'app/assets/javascripts/ide/components/commit_sidebar/list_item.vue' do
- element :file_to_commit_content
- end
-
- # Used for stablility, due to feature_caching of vscode_web_ide
- def wait_until_ide_loads
- Support::Waiter.wait_until(
- sleep_interval: 2,
- max_duration: 120,
- reload_page: page,
- retry_on_exception: true
- ) do
- has_element?(:commit_mode_tab)
- end
- end
-
- def has_file?(file_name)
- within_element(:file_list_container) do
- has_element?('file-row-name-container', file_name: file_name)
- end
- end
-
- def has_file_to_commit?(file_name)
- has_element?(:file_to_commit_content, file_name: file_name)
- end
-
- def has_project_path?(project_path)
- has_element?(:project_path_content, project_path: project_path)
- end
-
- def has_file_addition_icon?(file_name)
- within_element(:file_row_container, file_name: file_name) do
- has_element?(:changed_file_icon_content, title: 'Added')
- end
- end
-
- def has_folder_icon?(file_name)
- within_element(:file_row_container, file_name: file_name) do
- has_element?(:folder_icon_content)
- end
- end
-
- def has_download_button?(file_name)
- click_element(:file_row_container, file_name: file_name)
- within_element(:preview_container) do
- has_element?(:download_button)
- end
- end
-
- def has_image_viewer?(file_name)
- click_element(:file_row_container, file_name: file_name)
- within_element(:preview_container) do
- has_element?(:image_viewer_container)
- end
- end
-
- def has_file_content?(file_name, file_content)
- click_element(:file_row_container, file_name: file_name)
- within_element(:editor_container) do
- has_text?(file_content)
- end
- end
-
- def go_to_project
- click_element(:project_path_content, Page::Project::Show)
- end
-
- def create_new_file_from_template(file_name, template)
- click_element(:new_file_button, Page::Component::WebIDE::Modal::CreateNewFile)
-
- within_element(:template_list_content) do
- click_on file_name
- rescue Capybara::ElementNotFound
- raise ElementNotFound, %(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
- end
-
- # Wait for the modal to fade out too
- has_no_element?(:new_file_modal)
-
- wait_until(reload: false) do
- within_element(:file_templates_container) do
- click_element :file_template_dropdown
- fill_element :dropdown_filter_input, template
-
- begin
- click_on template
- rescue Capybara::ElementNotFound
- raise ElementNotFound, %(Couldn't find template "#{template}" for #{file_name}. Please confirm that it exists in the list of templates.)
- end
- end
- end
- end
-
- def commit_sha
- return unless has_element?(:commit_sha_content, wait: 0)
-
- find_element(:commit_sha_content).text
- end
-
- def commit_changes(commit_message = nil, open_merge_request: false, wait_for_success: true)
- # Clicking :begin_commit_button switches from the
- # edit to the commit view
- click_element(:begin_commit_button)
- active_element?(:commit_mode_tab)
-
- original_commit = commit_sha
-
- # After clicking :begin_commit_button, there is an animation
- # that hides :begin_commit_button and shows :commit_button
- #
- # Wait for the animation to complete before clicking :commit_button
- # otherwise the click will quietly do nothing.
- wait_until(reload: false) do
- has_no_element?(:begin_commit_button) &&
- has_element?(:commit_button)
- end
-
- if commit_message
- fill_element(:ide_commit_message_field, commit_message)
- end
-
- if open_merge_request
- click_element(:commit_button, Page::MergeRequest::New)
- else
- # Click :commit_button and keep retrying just in case part of the
- # animation is still in process even when the buttons have the
- # expected visibility.
- commit_success = retry_until(sleep_interval: 5) do
- within_element(:commit_to_current_branch_radio_container) do
- choose_element(:commit_type_radio, true)
- end
- click_element(:commit_button) if has_element?(:commit_button)
-
- break unless wait_for_success
-
- # If this is the first commit, the commit SHA only appears after reloading
- wait_until(reload: true) do
- active_element?(:edit_mode_tab) && commit_sha != original_commit
- end
- end
-
- if wait_for_success
- raise "The changes do not appear to have been committed successfully." unless commit_success
- end
- end
- end
-
- def add_to_modified_content(content)
- finished_loading?
- modified_text_area.click
- modified_text_area.set content
- end
-
- def modified_text_area
- wait_for_animated_element(:editor_container)
- within_element(:editor_container) do
- find('.modified textarea.inputarea')
- end
- end
-
- def create_first_file(file_name)
- click_element(:first_file_button, Page::Component::WebIDE::Modal::CreateNewFile)
- fill_element('file-name-field', file_name)
- click_button('Create file')
- end
-
- def add_file(file_name, file_text)
- click_element(:new_file_button, Page::Component::WebIDE::Modal::CreateNewFile)
- fill_element('file-name-field', file_name)
- click_button('Create file')
- wait_until(reload: false) { has_file?(file_name) }
- within_element(:editor_container) do
- find('textarea.inputarea').click.set(file_text)
- end
- end
-
- def add_directory(directory_name)
- click_element(:new_directory_button, Page::Component::WebIDE::Modal::CreateNewFile)
- fill_element('file-name-field', directory_name)
- click_button('Create directory')
- wait_until(reload: false) { has_file?(directory_name) }
- end
-
- def rename_file(file_name, new_file_name)
- click_element('file-row-name-container', file_name: file_name)
- click_element(:dropdown_button)
- click_element(:rename_move_button, Page::Component::WebIDE::Modal::CreateNewFile)
- fill_element('file-name-field', new_file_name)
- click_button('Rename file')
- end
-
- def fork_project!
- wait_until(reload: false) do
- has_element?(:confirm_fork_modal)
- end
- click_element(:fork_project_button)
- # wait for the fork to be created
- wait_until(reload: true) do
- has_element?(:file_list_container)
- end
- end
-
- def upload_file(file_path)
- within_element(:file_list_container) do
- find_element(:file_upload_field, visible: false).send_keys(file_path)
- end
- end
-
- def delete_file(file_name)
- click_element('file-row-name-container', file_name: file_name)
- click_element(:dropdown_button)
- click_element(:delete_button)
- end
-
- def switch_to_commit_tab
- click_element(:commit_mode_tab)
- end
-
- def select_file(file_name)
- # wait for the list of files to load
- wait_until(reload: true) do
- has_element?('file-row-name-container', file_name: file_name)
- end
- click_element('file-row-name-container', file_name: file_name)
- end
-
- def link_line(line_number)
- previous_url = page.current_url
- wait_for_animated_element(:editor_container)
- within_element(:editor_container) do
- find('.line-numbers', text: line_number).hover.click
- end
- wait_until(max_duration: 5, reload: false) do
- page.current_url != previous_url
- end
- page.current_url.to_s
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/page/project/web_ide/vscode.rb b/qa/qa/page/project/web_ide/vscode.rb
index 63dc75b523f..d7fbd3b0458 100644
--- a/qa/qa/page/project/web_ide/vscode.rb
+++ b/qa/qa/page/project/web_ide/vscode.rb
@@ -16,8 +16,11 @@ module QA
end
def right_click_file_explorer
- has_element?('div.monaco-list-rows')
- find_element('div[aria-label="Files Explorer"]').right_click
+ page.find('.explorer-folders-view', visible: true).right_click
+ end
+
+ def has_file?(file_name)
+ has_element?("div[aria-label='#{file_name}']")
end
def open_file_from_explorer(file_name)
@@ -36,19 +39,15 @@ module QA
has_element?('div.menu-item-check')
end
- def click_new_folder_menu_item
- click_element('span[aria-label="New Folder..."]')
- end
-
- def has_committed_and_pushed_successfully?
- page.has_css?('.span[title="Success! Your changes have been committed."]')
+ def click_menu_item(item)
+ click_element("li[title='#{item}']")
end
def click_upload_menu_item
click_element('span[aria-label="Upload..."]')
end
- def enter_new_folder_text_input(name)
+ def enter_text_for_input(name)
find_element('input[type="text"]')
send_keys(name, :enter)
end
@@ -93,28 +92,38 @@ module QA
end
def click_continue_with_existing_branch
- page.find('.monaco-button[title="Continue"]').click
+ click_element('.monaco-button[title="Continue"]')
end
def has_branch_input_field?
has_element?('input[aria-label="input"]')
end
+ def has_committed_successfully?
+ has_element?('.span[title="Success! Your changes have been committed."]')
+ end
+
def has_message?(content)
within_vscode_editor do
has_text?(content)
end
end
+ def close_ide_tab
+ page.execute_script "window.close();" if page.current_url.include?('ide')
+ end
+
+ def ide_tab_closed?
+ within_vscode_editor do
+ has_file_explorer?
+ end
+ end
+
def within_vscode_editor(&block)
iframe = find('#ide iframe')
page.within_frame(iframe, &block)
end
- def click_new_file_menu_item
- page.find('[aria-label="New File..."]').click
- end
-
def switch_to_original_window
page.driver.browser.switch_to.window(page.driver.browser.window_handles.first)
end
@@ -122,21 +131,16 @@ module QA
def create_new_file_from_template(filename, template)
within_vscode_editor do
Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do
- click_new_file_menu_item
- enter_new_file_text_input(filename)
+ click_menu_item("New File...")
+ enter_text_for_input(filename)
page.within('div.editor-container') do
page.find('textarea.inputarea.monaco-mouse-cursor-text').send_keys(template)
end
- page.has_content?(filename)
+ has_text?(filename)
end
end
end
- def enter_new_file_text_input(name)
- page.find('.explorer-item-edited', visible: true)
- send_keys(name, :enter)
- end
-
# Used for stablility, due to feature_caching of vscode_web_ide
def wait_for_ide_to_load
page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
@@ -156,25 +160,24 @@ module QA
end
end
- def create_new_folder(name)
- within_vscode_editor do
- # Use for stability, WebIDE inside an iframe is finnicky, webdriver sometimes moves too fast
- Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do
- right_click_file_explorer
- has_right_click_menu_item?
- click_new_folder_menu_item
- # Verify New Folder button is triggered and textbox is waiting for input
- enter_new_folder_text_input(name)
- has_text?(name)
- end
- end
+ def create_new_folder(folder_name)
+ create_item("New Folder...", folder_name)
+ end
+
+ def create_new_file(file_name)
+ create_item("New File...", file_name)
end
- def commit_and_push(file_name)
+ def commit_and_push_to_new_branch(file_name)
commit_toggle(file_name)
push_to_new_branch
end
+ def commit_and_push_to_existing_branch(file_name)
+ commit_toggle(file_name)
+ push_to_existing_branch
+ end
+
def commit_toggle(message)
within_vscode_editor do
if has_commit_pending_tab?
@@ -194,23 +197,26 @@ module QA
def push_to_existing_branch
within_vscode_editor do
click_continue_with_existing_branch
- has_committed_and_pushed_successfully?
+ has_committed_successfully?
end
end
def push_to_new_branch
within_vscode_editor do
- click_new_branch
+ page.find('.monaco-button[title="Create new branch"]').click
has_branch_input_field?
# Typing enter to 'New branch name' popup to take the default branch name
send_keys(:enter)
+ has_committed_successfully?
end
end
def create_merge_request
within_vscode_editor do
- has_element?('div[title="GitLab Web IDE Extension (Extension)"]')
- click_element('.monaco-button[title="Create MR"]')
+ within_element('.notification-toast-container') do
+ has_element?('div[title="GitLab Web IDE Extension (Extension)"]')
+ click_element('.monaco-text-button[title="Create MR"]')
+ end
end
end
@@ -221,7 +227,9 @@ module QA
page.execute_script("document.body.removeChild = function(){};")
# Use for stability, WebIDE inside an iframe is finnicky, webdriver sometimes moves too fast
- Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do
+ Support::Retrier.retry_until(
+ max_attempts: 5, retry_on_exception: true, sleep_interval: 2
+ ) do
right_click_file_explorer
has_right_click_menu_item?
click_upload_menu_item
@@ -237,15 +245,31 @@ module QA
open_file_from_explorer(file_name)
click_inside_editor_frame
within_file_editor do
- send_keys(:enter, :enter, prompt_data)
+ wait_until_code_suggestions_enabled
+ send_keys(:enter, :enter)
+
+ # Send keys one at a time to allow suggestions request to be triggered
+ prompt_data.each_char { |c| send_keys(c) }
end
end
end
+ def wait_until_code_suggestions_enabled
+ wait_until(max_duration: 30, message: 'Wait for Code Suggestions extension to be enabled') do
+ has_code_suggestions_status?('enabled')
+ end
+ end
+
+ def has_code_suggestions_status?(status)
+ page.document.has_css?(
+ "#GitLab\\.gitlab-workflow\\.gl\\.status\\.code_suggestions[aria-label~=#{status.downcase}]"
+ )
+ end
+
def verify_prompt_appears_and_accept(pattern)
within_vscode_editor do
within_file_editor do
- Support::Waiter.wait_until(max_duration: 30) do
+ Support::Waiter.wait_until(max_duration: 60, message: 'Wait for suggestion to appear') do
page.text.match?(pattern)
end
send_keys(:tab)
@@ -260,6 +284,20 @@ module QA
end
end
end
+
+ private
+
+ def create_item(click_item, item_name)
+ within_vscode_editor do
+ # Use for stability, WebIDE inside an iframe is finnicky, webdriver sometimes moves too fast
+ Support::Waiter.wait_until(max_duration: 20, retry_on_exception: true) do
+ click_menu_item(click_item)
+ # Verify the button is triggered and textbox is waiting for input
+ enter_text_for_input(item_name)
+ has_text?(item_name)
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
index f9e5e97b96f..5d856875e0b 100644
--- a/qa/qa/page/search/results.rb
+++ b/qa/qa/page/search/results.rb
@@ -4,11 +4,6 @@ module QA
module Page
module Search
class Results < QA::Page::Base
- view 'app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue' do
- element :code_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern
- element :projects_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern
- end
-
view 'app/views/search/results/_blob_data.html.haml' do
element :result_item_content
element :file_title_content
@@ -20,7 +15,7 @@ module QA
end
def switch_to_code
- click_element(:nav_item_link, submenu_item: 'Code')
+ click_element('nav-item-link', submenu_item: 'Code')
end
def switch_to_projects
diff --git a/qa/qa/page/sub_menus/common.rb b/qa/qa/page/sub_menus/common.rb
index 4cfd4f04cbe..0457d1f29fc 100644
--- a/qa/qa/page/sub_menus/common.rb
+++ b/qa/qa/page/sub_menus/common.rb
@@ -8,18 +8,23 @@ module QA
super
base.class_eval do
- prepend Mobile::Page::SubMenus::Common if QA::Runtime::Env.mobile_layout?
-
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
- element :navbar
+ element 'super-sidebar'
+ end
+
+ view 'app/assets/javascripts/super_sidebar/components/create_menu.vue' do
+ element 'new-menu-toggle'
end
- end
- end
- def within_sidebar(&block)
- wait_for_requests
+ view 'app/assets/javascripts/super_sidebar/components/menu_section.vue' do
+ element 'menu-section-button'
+ element 'menu-section'
+ end
- within_element(:navbar, &block)
+ view 'app/assets/javascripts/super_sidebar/components/nav_item.vue' do
+ element 'nav-item-link'
+ end
+ end
end
private
@@ -36,16 +41,15 @@ module QA
# Open sidebar navigation submenu
#
# @param [String] parent_menu_name
- # @param [String] parent_section_id
# @param [String] sub_menu
# @return [void]
def open_submenu(parent_menu_name, sub_menu)
# prevent closing sub-menu if it was already open
- unless has_element?(:menu_section, section_name: parent_menu_name, wait: 0)
- click_element(:menu_section_button, section_name: parent_menu_name)
+ unless has_element?('menu-section', section_name: parent_menu_name, wait: 0)
+ click_element('menu-section-button', section_name: parent_menu_name)
end
- within_element(:menu_section, section_name: parent_menu_name) do
+ within_element('menu-section', section_name: parent_menu_name) do
click_element('nav-item-link', submenu_item: sub_menu)
end
end
diff --git a/qa/qa/page/sub_menus/deploy.rb b/qa/qa/page/sub_menus/deploy.rb
index c930cd6b6df..4494395ba45 100644
--- a/qa/qa/page/sub_menus/deploy.rb
+++ b/qa/qa/page/sub_menus/deploy.rb
@@ -14,6 +14,10 @@ module QA
open_deploy_submenu('Container Registry')
end
+ def go_to_pages_settings
+ open_deploy_submenu('Pages')
+ end
+
private
def open_deploy_submenu(sub_menu)
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 14ee09541f4..01786d3d4b6 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -285,6 +285,10 @@ module QA
end
end
+ def all_branches
+ get(request_url(api_repository_branches_path))
+ end
+
def has_tags?(tags)
tags.all? do |tag|
response = get(request_url("#{api_repository_tags_path}/#{tag}"))
@@ -338,17 +342,7 @@ module QA
raise ResourceQueryError, "Could not get import status. Request returned (#{response.code}): `#{response}`."
end
- result = parse_body(response)
-
- if result[:import_status] == "failed"
- Runtime::Logger.error(<<~ERR)
- Import of project '#{full_path}' failed!
- error: '#{result[:import_error]}'
- failed relations: '#{result[:failed_relations]}'
- ERR
- end
-
- result
+ parse_body(response)
end
def commits(auto_paginate: false, attempts: 0)
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index 855e1edf3ef..f1a623d9ddf 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -6,8 +6,7 @@ module QA
attr_accessor :issue_events_import,
:full_notes_import,
:attachments_import,
- :allow_partial_import,
- :additional_access_tokens
+ :allow_partial_import
attribute :github_repo_id do
github_client.repository(github_repository_path).id
@@ -62,7 +61,6 @@ module QA
new_name: name,
target_namespace: @personal_namespace || group.full_path,
personal_access_token: github_personal_access_token,
- additional_access_tokens: additional_access_tokens,
ci_cd_only: false,
optional_stages: {
single_endpoint_issue_events_import: issue_events_import,
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index ce970d02e0a..1fad0b76645 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -43,6 +43,7 @@ module QA
end
def self.configure! # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ return if QA::Runtime::Env.dry_run
return if @configured
RSpec.configure do |config|
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 2f9812a30ba..99b819017e3 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -502,10 +502,6 @@ module QA
ENV['QA_GITHUB_ACCESS_TOKEN'].to_s.strip
end
- def github_additional_access_tokens
- ENV['QA_ADDITIONAL_GITHUB_ACCESS_TOKENS']
- end
-
def require_github_access_token!
return unless github_access_token.empty?
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index 56ab4e14352..92c852e9010 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -4,10 +4,10 @@ module QA
module Scenario
class Template
class << self
- def perform(*args)
+ def perform(...)
new.tap do |scenario|
yield scenario if block_given?
- break scenario.perform(*args)
+ break scenario.perform(...)
end
end
@@ -21,7 +21,10 @@ module QA
end
def perform(options, *args)
- define_gitlab_address(options, args)
+ define_gitlab_address(args)
+
+ # Store passed options globally
+ Support::GlobalOptions.set(options)
# Save the scenario class name
Runtime::Scenario.define(:klass, self.class.name)
@@ -29,53 +32,24 @@ module QA
# Set large setup attribute
Runtime::Scenario.define(:large_setup?, args.include?('can_use_large_setup'))
- ##
- # Configure browser
- #
- Runtime::Browser.configure!
-
- ##
- # Perform before hooks, which are different for CE and EE
- #
- QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run
-
- Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
-
- if options.key?(:disable_feature) && (@feature_enabled = Runtime::Feature.enabled?(options[:disable_feature]))
- Runtime::Feature.disable(options[:disable_feature])
- end
-
- Runtime::Feature.set(options[:set_feature_flags]) if options.key?(:set_feature_flags)
-
Specs::Runner.perform do |specs|
specs.tty = true
specs.tags = self.class.focus
specs.options = args if args.any?
end
- ensure
- Runtime::Feature.disable(options[:enable_feature]) if options.key?(:enable_feature)
- Runtime::Feature.enable(options[:disable_feature]) if options.key?(:disable_feature) && @feature_enabled
- end
-
- def extract_address(name, options)
- address = options[name]
- validate_address(name, address)
-
- Runtime::Scenario.define(name, address)
end
private
- delegate :define_gitlab_address_attribute!, to: 'QA::Support::GitlabAddress'
+ delegate :define_gitlab_address_attribute!, to: QA::Support::GitlabAddress
# Define gitlab address attribute
#
# Use first argument if a valid address, else use named argument or default to environment variable
#
- # @param [Hash] options
# @param [Array] args
# @return [void]
- def define_gitlab_address(options, args)
+ def define_gitlab_address(args)
address_from_opt = Runtime::Scenario.attributes[:gitlab_address]
return define_gitlab_address_attribute!(args.shift) if args.first && Runtime::Address.valid?(args.first)
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index 688fede1b2e..ad84f99128b 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -36,7 +36,7 @@ module QA
For more help see documentation in
https://docs.gitlab.com/ee/development/testing_guide/end_to_end/page_objects.html
- or ask for help on #quality channel on Slack (GitLab Team only).
+ or ask for help on #test-platform channel on Slack (GitLab Team only).
If you are not a Team Member, and you still need help to
contribute, please open an issue in GitLab QA issue tracker.
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 7ae676088d0..a67a8163daf 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -91,6 +91,7 @@ module QA
args << '--docker-privileged=true'
args << "--docker-network-mode=#{network}"
args << "--docker-volumes=/certs/client"
+ args << "--docker-extra-hosts=gdk.test:#{gdk_host_ip}" if gdk_network
end
<<~CMD.strip
diff --git a/qa/qa/service/docker_run/smocker.rb b/qa/qa/service/docker_run/smocker.rb
index 1f205440f4b..da01b4a5223 100644
--- a/qa/qa/service/docker_run/smocker.rb
+++ b/qa/qa/service/docker_run/smocker.rb
@@ -42,6 +42,8 @@ module QA
def host_name
@host_name ||= if QA::Runtime::Env.running_in_ci? || QA::Runtime::Env.qa_hostname
+ return host_ip if gdk_network
+
"#{@name}.#{@network_cache}"
else
host_ip
diff --git a/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb b/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
index a3b14566153..3e190608268 100644
--- a/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
+++ b/qa/qa/specs/features/api/10_govern/group_access_token_spec.rb
@@ -3,18 +3,20 @@
module QA
RSpec.describe 'Govern' do
describe 'Group access token', product_group: :authentication do
+ include QA::Support::Helpers::Project
+
let(:group_access_token) { create(:group_access_token) }
let(:api_client) { Runtime::API::Client.new(:gitlab, personal_access_token: group_access_token.token) }
let(:project) do
- Resource::Project.fabricate! do |project|
- project.name = 'project-for-group-access-token'
- project.group = group_access_token.group
- project.initialize_with_readme = true
- end
+ create(:project, name: 'project-for-group-access-token', group: group_access_token.group)
+ end
+
+ before do
+ wait_until_project_is_ready(project)
end
it(
- 'can be used to create a file via the project API',
+ 'can be used to create a file via the project API', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367064'
) do
expect do
@@ -29,12 +31,8 @@ module QA
end
it(
- 'can be used to commit via the API',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367067',
- quarantine: {
- type: :flaky,
- issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/396615"
- }
+ 'can be used to commit via the API', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367067'
) do
expect do
create(:commit,
diff --git a/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb b/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
index cb7e4849d0f..8f4a04ef389 100644
--- a/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
+++ b/qa/qa/specs/features/api/10_govern/project_access_token_spec.rb
@@ -3,12 +3,18 @@
module QA
RSpec.describe 'Govern' do
describe 'Project access token', product_group: :authentication do
- let!(:project) { create(:project, name: "project-to-test-project-access-token-#{SecureRandom.hex(4)}") }
+ include QA::Support::Helpers::Project
+
+ let!(:project) { create(:project, name: "project-to-test-project-access-token") }
let!(:project_access_token) { create(:project_access_token, project: project) }
let!(:user_api_client) { Runtime::API::Client.new(:gitlab, personal_access_token: project_access_token.token) }
+ before do
+ wait_until_project_is_ready(project)
+ end
+
context 'for the same project' do
- it 'can be used to create a file via the project API',
+ it 'can be used to create a file via the project API', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347858' do
expect do
create(:file,
@@ -41,14 +47,18 @@ module QA
context 'for a different project' do
let(:different_project) do
- create(:project, name: "different-project-to-test-project-access-token-#{SecureRandom.hex(4)}")
+ create(:project, name: "different-project-to-test-project-access-token")
+ end
+
+ before do
+ wait_until_project_is_ready(different_project)
end
after do
different_project.remove_via_api!
end
- it 'cannot be used to create a file via the project API',
+ it 'cannot be used to create a file via the project API', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347860' do
expect do
create(:file,
@@ -58,7 +68,7 @@ module QA
end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden/)
end
- it 'cannot be used to commit via the API',
+ it 'cannot be used to commit via the API', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347861' do
expect do
create(:commit,
diff --git a/qa/qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb b/qa/qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb
index c855c5f7c74..54979faad2e 100644
--- a/qa/qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb
+++ b/qa/qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb
@@ -13,6 +13,10 @@ module QA
project.initialize_with_readme = true
end
+ # Debugging what branches are present
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/431474
+ QA::Runtime::Logger.info("project.all_branches.body #{project.all_branches.body}")
+
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.new_branch = false
diff --git a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb
index 9d0d81bdd91..572b7c61767 100644
--- a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb
@@ -31,7 +31,7 @@ module QA
stats = imported_project.project_import_status.dig(:stats, :imported)
expect(stats).to eq(
issue: 1,
- issue_event: 16,
+ issue_event: 10,
pull_request: 1,
pull_request_review: 2,
pull_request_review_request: 1,
@@ -168,19 +168,6 @@ module QA
{ name: "add_milestone", label: "0.0.1" }
]
)
- # TODO: reenable once https://gitlab.com/gitlab-org/gitlab/-/issues/386714 fixed
- # currently this doesn't work as expected if reviewer is not matched by public email
- # event for assigning approver is created with reviewer being user doing import but mr actually doesn't
- # contain reviewers or the approved state
- #
- # reviews = merge_request.reviews.map do |review|
- # {
- # id: review.dig(:user, :id),
- # username: review.dig(:user, :username),
- # state: review[:state]
- # }
- # end
- # expect(reviews).to eq([{ id: user.id, username: user.username, state: "approved" }])
end
def verify_release_import
diff --git a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
index 9c0720aa185..83beb8faad5 100644
--- a/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb
@@ -10,14 +10,15 @@ require "etc"
# Because of this, all expectation check for inclusion rather than exact match to avoid failures if extra issues,
# comments, events got created while import was running.
-# rubocop:disable Rails/Pluck
+# rubocop:disable Rails/Pluck -- false positive matches
+# rubocop:disable RSpec/MultipleMemoizedHelpers -- slightly specific test which relies on instance variables to track metrics
module QA
RSpec.describe 'Manage', :github, requires_admin: 'creates users',
only: { condition: -> { ENV["CI_PROJECT_NAME"] == "import-metrics" } },
custom_test_metrics: {
tags: { import_type: ENV["QA_IMPORT_TYPE"], import_repo: ENV["QA_LARGE_IMPORT_REPO"] || "rspec/rspec-core" }
} do
- describe 'Project import', product_group: :import_and_integrate do # rubocop:disable RSpec/MultipleMemoizedHelpers
+ describe 'Project import', product_group: :import_and_integrate do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:user) { create(:user, api_client: api_client) }
let!(:user_api_client) do
@@ -44,13 +45,13 @@ module QA
let(:logger) { Runtime::Logger.logger }
let(:gitlab_address) { QA::Runtime::Scenario.gitlab_address.chomp("/") }
let(:dummy_url) { "https://example.com" } # this is used to replace all dynamic urls in descriptions and comments
- let(:api_request_params) { { auto_paginate: true, attempts: 2 } }
+ let(:api_request_params) { { auto_paginate: true, attempts: 3 } }
let(:created_by_pattern) { /\*Created by: \S+\*\n\n/ }
let(:suggestion_pattern) { /suggestion:-\d+\+\d+/ }
let(:gh_link_pattern) { %r{https://github.com/#{github_repo}/(issues|pull)} }
let(:gl_link_pattern) { %r{#{gitlab_address}/#{imported_project.path_with_namespace}/-/(issues|merge_requests)} }
- # rubocop:disable Lint/MixedRegexpCaptureTypes
+ # rubocop:disable Lint/MixedRegexpCaptureTypes -- optional capture groups
let(:event_pattern) do
Regexp.union(
[
@@ -127,7 +128,7 @@ module QA
max: 3,
interval: 1,
retry_block: ->(exception:, **) { logger.warn("Request to GitHub failed: '#{exception}', retrying") },
- exceptions: [Octokit::InternalServerError, Octokit::ServerError]
+ exceptions: [Faraday::ServerError, Faraday::ConnectionFailed, Faraday::SSLError]
)
builder.use(Faraday::Response::RaiseError) # faraday retry swallows errors, so it needs to be re-raised
end
@@ -160,6 +161,10 @@ module QA
end
end
+ let(:gh_milestone_titles) do
+ gh_milestones.map { |milestone| milestone[:title] }
+ end
+
let(:gh_all_issues) do
logger.info("= Fetching issues and prs =")
with_paginated_request { github_client.list_issues(github_repo, state: 'all') }
@@ -198,7 +203,6 @@ module QA
project.add_name_uuid = false
project.name = 'imported-project'
project.github_personal_access_token = Runtime::Env.github_access_token
- project.additional_access_tokens = Runtime::Env.github_additional_access_tokens
project.github_repository_path = github_repo
project.personal_namespace = user.username
project.api_client = Runtime::API::Client.new(user: user)
@@ -207,6 +211,8 @@ module QA
end
end
+ let(:status_details) { (@import_status || {}).slice(:import_error, :failed_relations, :correlation_id) }
+
before do
QA::Support::Helpers::ImportSource.enable('github')
end
@@ -218,7 +224,7 @@ module QA
importer: :github,
import_finished: false,
import_time: Time.now - @start
- }))
+ }.merge(status_details)))
end
# add additional import time metric
@@ -229,16 +235,14 @@ module QA
status: example.exception ? "failed" : "passed",
import_time: @import_time,
import_finished: true,
- errors: imported_project.project_import_status[:failed_relations],
reported_stats: @stats
- }))
+ }.merge(status_details)))
end
save_data_json(test_result_data({
status: example.exception ? "failed" : "passed",
import_time: @import_time,
import_finished: true,
- errors: imported_project.project_import_status[:failed_relations],
reported_stats: @stats,
source: {
data: {
@@ -272,7 +276,7 @@ module QA
mrs: @mr_diff,
issues: @issue_diff
}
- }))
+ }.merge(status_details)))
end
it(
@@ -292,14 +296,18 @@ module QA
fetch_github_objects unless only_stats_comparison
import_status = -> {
- imported_project.project_import_status.then do |status|
- @stats = status[:stats]&.slice(:fetched, :imported)
-
- # fail fast if import explicitly failed
- raise "Import of '#{imported_project.full_path}' failed!" if status[:import_status] == 'failed'
-
- status[:import_status]
+ @import_status = Support::Retrier.retry_on_exception(
+ sleep_interval: 1,
+ log: false,
+ message: "Fetching import status"
+ ) do
+ imported_project.project_import_status
end
+ @stats = @import_status[:stats]&.slice(:fetched, :imported)
+ # fail fast if import explicitly failed
+ raise "Import of '#{imported_project.full_path}' failed!" if @import_status[:import_status] == 'failed'
+
+ @import_status[:import_status]
}
logger.info("== Waiting for import to be finished ==")
@@ -362,7 +370,14 @@ module QA
logger.info("== Verifying repository import ==")
expect(imported_project.description).to eq(gh_repo.description)
expect(gl_branches).to include(*gh_branches)
- expect(gl_commits).to include(*gh_commits)
+
+ # When testing with very large repositories, comparing with include will raise 'stack level too deep' error
+ # Compare just the size in this case
+ if gh_commits.size > 10000
+ expect(gl_commits.size).to be >= gh_commits.size
+ else
+ expect(gl_commits).to include(*gh_commits)
+ end
end
# Verify imported labels
@@ -425,6 +440,7 @@ module QA
# @return [Array]
def fetch_issuable_events(id)
with_paginated_request { github_client.issue_events(github_repo, id) }
+ .reject { |event| deleted_milestone_event?(event) }
.map { |event| event[:event] }
.reject { |event| unsupported_events.include?(event) }
end
@@ -523,7 +539,7 @@ module QA
def gl_branches
@gl_branches ||= begin
logger.debug("= Fetching branches =")
- imported_project.repository_branches(auto_paginate: true).map { |b| b[:name] }
+ imported_project.repository_branches(auto_paginate: true, attempts: 3).map { |b| b[:name] }
end
end
@@ -533,7 +549,7 @@ module QA
def gl_commits
@gl_commits ||= begin
logger.debug("= Fetching commits =")
- imported_project.commits(auto_paginate: true, attempts: 2).map { |c| c[:id] }
+ imported_project.commits(auto_paginate: true, attempts: 3).map { |c| c[:id] }
end
end
@@ -543,7 +559,7 @@ module QA
def gl_labels
@gl_labels ||= begin
logger.debug("= Fetching labels =")
- imported_project.labels(auto_paginate: true).map { |label| label.slice(:name, :color) }
+ imported_project.labels(auto_paginate: true, attempts: 3).map { |label| label.slice(:name, :color) }
end
end
@@ -553,7 +569,7 @@ module QA
def gl_milestones
@gl_milestones ||= begin
logger.debug("= Fetching milestones =")
- imported_project.milestones(auto_paginate: true).map { |ms| ms.slice(:title, :description) }
+ imported_project.milestones(auto_paginate: true, attempts: 3).map { |ms| ms.slice(:title, :description) }
end
end
@@ -633,7 +649,8 @@ module QA
def events(comments, label_events, state_events, milestone_events)
mapped_label_events = label_events.map { |event| event_mapping["label_#{event[:action]}"] }
mapped_milestone_events = milestone_events.map { |event| event_mapping["milestone_#{event[:action]}"] }
- mapped_state_event = state_events.map { |event| event[:state] }
+ # merged events are fetched through comments so duplicates need to be removed
+ mapped_state_event = state_events.map { |event| event[:state] }.reject { |state| state == "merged" }
mapped_comment_events = comments.map do |c|
event_mapping[c[:body].match(event_pattern)&.named_captures&.fetch("event", nil)]
end
@@ -641,6 +658,16 @@ module QA
[*mapped_label_events, *mapped_milestone_events, *mapped_state_event, *mapped_comment_events].compact
end
+ # Check if a milestone event is from a deleted milestone
+ #
+ # @param [Hash] event
+ # @return [Boolean]
+ def deleted_milestone_event?(event)
+ return false if %w[milestoned demilestoned].exclude?(event[:event])
+
+ gh_milestone_titles.exclude?(event[:milestone][:title])
+ end
+
# Normalize comments and make them directly comparable
#
# * remove created by prefixes
@@ -719,3 +746,4 @@ module QA
end
end
# rubocop:enable Rails/Pluck
+# rubocop:enable RSpec/MultipleMemoizedHelpers
diff --git a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb
index 06990c0c74b..c55d473fd41 100644
--- a/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb
@@ -101,11 +101,12 @@ module QA
let(:hook_trigger_times) { 5 }
let(:disabled_after) { 4 }
+ before do
+ Runtime::Feature.enable(:auto_disabling_web_hooks)
+ end
+
it 'hook is auto-disabled',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389595', quarantine: {
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/393274',
- type: :investigating
- } do
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/389595' do
Resource::ProjectWebHook.setup(fail_mock, session: session, issues: true) do |webhook, smocker|
hook_trigger_times.times do
create(:issue, project: webhook.project)
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
index 6ea2047fed7..6426872e76d 100644
--- 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
@@ -66,7 +66,7 @@ module QA
let(:source_project) { source_group.projects(auto_paginate: true).find { |project| project.name == gitlab_source_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_labels) { source_project.labels(auto_paginate: true).map { |l| l.except(:id, :description_html) } }
let(:source_milestones) { source_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
let(:source_mrs) { fetch_mrs(source_project, source_api_client, transform_urls: true) }
let(:source_issues) { fetch_issues(source_project, source_api_client, transform_urls: true) }
@@ -85,16 +85,16 @@ module QA
# Imported objects
#
- let(:imported_project) { imported_group.projects(auto_paginate: true).find { |project| project.name == gitlab_source_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(:imported_project) { imported_group.projects(auto_paginate: true, attempts: 3).find { |project| project.name == gitlab_source_project }.reload! }
+ let(:branches) { imported_project.repository_branches(auto_paginate: true, attempts: 3).map { |b| b[:name] } }
+ let(:commits) { imported_project.commits(auto_paginate: true, attempts: 3).map { |c| c[:id] } }
+ let(:labels) { imported_project.labels(auto_paginate: true, attempts: 3).map { |l| l.except(:id, :description_html) } }
+ let(:milestones) { imported_project.milestones(auto_paginate: true, attempts: 3).map { |ms| ms.except(:id, :web_url, :project_id) } }
let(:mrs) { fetch_mrs(imported_project, api_client) }
let(:issues) { fetch_issues(imported_project, api_client) }
let(:pipelines) do
imported_project
- .pipelines(auto_paginate: true)
+ .pipelines(auto_paginate: true, attempts: 3)
.sort_by { |pipeline| pipeline[:created_at] }
.map { |pipeline| pipeline.except(:id, :web_url, :project_id) }
end
@@ -353,7 +353,7 @@ module QA
# @param [Boolean] transform_urls
# @return [Hash]
def fetch_mrs(project, client, transform_urls: false)
- imported_mrs = project.merge_requests(auto_paginate: true, attempts: 2)
+ imported_mrs = project.merge_requests(auto_paginate: true, attempts: 3)
Parallel.map(imported_mrs, in_threads: api_parallel_threads) do |mr|
resource = build(:merge_request, project: project, iid: mr[:iid], api_client: client)
@@ -364,7 +364,7 @@ module QA
body: sanitize_description(mr[:description], transform_urls) || '',
state: mr[:state],
comments: resource
- .comments(auto_paginate: true, attempts: 2)
+ .comments(auto_paginate: true, attempts: 3)
.map { |c| sanitize_comment(c[:body], transform_urls) }
}]
end.to_h
@@ -377,7 +377,7 @@ module QA
# @param [Boolean] transform_urls
# @return [Hash]
def fetch_issues(project, client, transform_urls: false)
- imported_issues = project.issues(auto_paginate: true, attempts: 2)
+ imported_issues = project.issues(auto_paginate: true, attempts: 3)
Parallel.map(imported_issues, in_threads: api_parallel_threads) do |issue|
resource = build(:issue, project: project, iid: issue[:iid], api_client: client)
@@ -388,7 +388,7 @@ module QA
state: issue[:state],
body: sanitize_description(issue[:description], transform_urls) || '',
comments: resource
- .comments(auto_paginate: true, attempts: 2)
+ .comments(auto_paginate: true, attempts: 3)
.map { |c| sanitize_comment(c[:body], transform_urls) }
}]
end.to_h
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb
index bde2393d4fa..ea2d23ef0c4 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb
@@ -26,7 +26,6 @@ module QA
let!(:target_sandbox) { source_sandbox }
let(:destination_group_path) { "target-group-for-import-#{SecureRandom.hex(4)}" }
- let(:cleanup!) { user.remove_via_api! }
it(
'successfully imports project',
diff --git a/qa/qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb
index 2f7cef2112e..c9f5fd40a78 100644
--- a/qa/qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb
@@ -31,7 +31,7 @@ module QA
end
it(
- 'creates, retrieves and deletes branches',
+ 'creates, retrieves and deletes branches', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347740'
) do
# Create branch
diff --git a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
index 3a6874cd587..00e3f90ba41 100644
--- a/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb
@@ -11,7 +11,8 @@ module QA
Runtime::ApplicationSettings.restore_application_settings(:default_branch_name)
end
- it 'sets the default branch name for a new project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347837' do
+ it 'sets the default branch name for a new project', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347837' do
project = create(:project, :with_readme, name: 'default-branch-name')
# It takes a moment to create the project. We wait until we
@@ -27,7 +28,8 @@ module QA
end
end
- it 'allows a project to be created via the CLI with a different default branch name', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347838' do
+ it 'allows a project to be created via the CLI with a different default branch name', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347838' do
project_name = "default-branch-name-via-cli-#{SecureRandom.hex(8)}"
group = create(:group)
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index 2aeb56c1c2b..c38d523c3b9 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -78,7 +78,8 @@ module QA
SVG
end
- it 'sets no-cache headers as expected', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347746' do
+ it 'sets no-cache headers as expected', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347746' do
create_project_request = Runtime::API::Request.new(@api_client, '/projects')
post create_project_request.url, path: project_name, name: project_name
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
index 9817915e9eb..a7af271a38e 100644
--- a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -28,7 +28,8 @@ module QA
end
end
- it 'download archives of each user project then check they are different', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347748' do
+ it 'download archives of each user project then check they are different', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347748' do
archive_checksums = {}
users.each do |user_key, user_info|
diff --git a/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb b/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb
index 72ce2dda053..ac69f0fb686 100644
--- a/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/storage_size_spec.rb
@@ -28,7 +28,7 @@ module QA
#
# There are unit tests to verify the accuracy of GitLab's determination of repo size, so for this test we
# attempt to detect large differences that could indicate a regression to previous behavior.
- it 'matches cloned repo usage to reported usage',
+ it 'matches cloned repo usage to reported usage', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/365196' do
project = create(:project, name: project_name)
diff --git a/qa/qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb b/qa/qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb
index 37c29715715..03a1d4f0c88 100644
--- a/qa/qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb
+++ b/qa/qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb
@@ -12,6 +12,8 @@ module QA
end
context 'when added to parent group' do
+ include QA::Support::Helpers::Project
+
let!(:parent_group_user) { create(:user, api_client: admin_api_client) }
let!(:parent_group_user_api_client) do
@@ -23,6 +25,8 @@ module QA
end
before do
+ wait_until_project_is_ready(sub_group_project)
+
parent_group.add_member(parent_group_user)
end
@@ -52,22 +56,27 @@ module QA
:reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363348'
) do
- expect do
- create(:file,
- api_client: parent_group_user_api_client,
- project: sub_group_project,
- branch: "new_branch_#{SecureRandom.hex(8)}")
- end.not_to raise_error
+ # Retry is needed due to delays with project authorization updates
+ # Long term solution to accessing the status of a project authorization update
+ # has been proposed in https://gitlab.com/gitlab-org/gitlab/-/issues/393369
+ QA::Support::Retrier.retry_on_exception(max_attempts: 30, sleep_interval: 2) do
+ expect do
+ create(:file,
+ api_client: parent_group_user_api_client,
+ project: sub_group_project,
+ branch: "new_branch_#{SecureRandom.hex(8)}")
+ end.not_to raise_error
+ end
end
it(
- 'is allowed to commit to sub-group project via the API',
+ 'is allowed to commit to sub-group project via the API', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349'
) do
# Retry is needed due to delays with project authorization updates
# Long term solution to accessing the status of a project authorization update
# has been proposed in https://gitlab.com/gitlab-org/gitlab/-/issues/393369
- QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 2) do
+ QA::Support::Retrier.retry_on_exception(max_attempts: 30, sleep_interval: 2) do
expect do
create(:commit,
api_client: parent_group_user_api_client,
diff --git a/qa/qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb b/qa/qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb
index 525b22c8a7d..6201ef7d58b 100644
--- a/qa/qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb
@@ -6,7 +6,7 @@ module QA
let(:group_access_token) { QA::Resource::GroupAccessToken.fabricate_via_browser_ui! }
it(
- 'can be created and revoked via the UI',
+ 'can be created and revoked via the UI', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367044'
) do
expect(group_access_token.token).not_to be_nil
diff --git a/qa/qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb b/qa/qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb
index b0e8c367924..bc2b80a3fc6 100644
--- a/qa/qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb
+++ b/qa/qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb
@@ -42,7 +42,7 @@ module QA
end
it(
- 'allows enforcing 2FA via UI and logging in with 2FA',
+ 'allows enforcing 2FA via UI and logging in with 2FA', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931'
) do
enforce_two_factor_authentication_on_group(group)
diff --git a/qa/qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb b/qa/qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb
index 142d4857d10..9df86c82377 100644
--- a/qa/qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb
@@ -8,7 +8,7 @@ module QA
let!(:user) { create(:user, :hard_delete, api_client: admin_api_client) }
it(
- 'can be created and revoked via the UI',
+ 'can be created and revoked via the UI', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/368888'
) do
impersonation_token = QA::Resource::ImpersonationToken.fabricate_via_browser_ui! do |impersonation_token|
diff --git a/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb
index 6dfc58fbfea..05d0bbda6c4 100644
--- a/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb
@@ -21,7 +21,7 @@ module QA
end
it(
- 'shows results for the original request and AJAX requests',
+ 'shows results for the original request and AJAX requests', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348030'
) do
# Issue pages always make AJAX requests
diff --git a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb
index 120da2990dd..1638aab0c88 100644
--- a/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb
@@ -12,7 +12,7 @@ module QA
end
it(
- 'has service ping toggle enabled',
+ 'has service ping toggle enabled', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335'
) do
Page::Admin::Settings::MetricsAndProfiling.perform do |setting|
diff --git a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb
index b2cb1cd309f..0eef51f5e2e 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb
@@ -3,11 +3,17 @@
module QA
RSpec.describe 'Plan', product_group: :product_planning do
describe 'Design Management' do
- let(:issue) { create(:issue) }
let(:design_filename) { 'banana_sample.gif' }
let(:design) { Runtime::Path.fixture('designs', design_filename) }
let(:annotation) { "This design is great!" }
+ let(:issue) do
+ create(
+ :issue,
+ project: create(:project, group: create(:group, path: "design-management-#{SecureRandom.hex(4)}"))
+ )
+ end
+
before do
Flow::Login.sign_in
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/pages/new_static_page_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/pages/new_static_page_spec.rb
index 579b6e43533..1d9acafb133 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/pages/new_static_page_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/pages/new_static_page_spec.rb
@@ -7,9 +7,8 @@ module QA
except: { job: 'review-qa-*' },
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/383215',
- type: :flaky
- },
- feature_flag: { name: 'show_pages_in_deployments_menu' } do
+ type: :broken
+ } do
# TODO: Convert back to :smoke once proved to be stable. Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300906
describe 'Pages', product_group: :knowledge do
let!(:project) { create(:project, name: 'gitlab-pages-projects', template_name: :plainhtml) }
@@ -40,7 +39,7 @@ module QA
end
Page::Project::Menu.perform(&:go_to_pages_settings)
- Page::Project::Settings::Pages.perform(&:go_to_access_page)
+ Page::Project::Pages.perform(&:go_to_access_page)
Support::Waiter.wait_until(
sleep_interval: 2,
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index b4416d8013e..c7faa97fd2b 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -22,10 +22,17 @@ module QA
])
merge_request.visit!
-
Page::MergeRequest::Show.perform do |mr_page|
- expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.', wait: 20)
- expect(mr_page).to be_fast_forward_not_possible
+ if mr_page.merge_blocked_component_ff_enabled?
+ expect(mr_page).to have_content('Merge blocked: 1 check failed', wait: 20)
+ mr_page.expand_merge_checks
+ expect(mr_page).to have_content('Merge request must be rebased, because a fast-forward merge is not possible.')
+ else
+ expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.', wait: 20)
+ expect(mr_page).to be_fast_forward_not_possible
+ page.refresh
+ end
+
expect(mr_page).not_to have_merge_button
expect(merge_request.project.commits.size).to eq(2)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 7d6746bf596..02359d1fb51 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -9,6 +9,11 @@ module QA
before do
Flow::Login.sign_in
+ # Since the test immediately navigates to the MR after pushing a commit,
+ # the MR is blocked for 10 seconds
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/431984
+ project.update_approval_configuration(reset_approvals_on_push: false)
+
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = 'to be squashed'
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index 15bd324da7c..bb98ab5c5be 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Git clone over HTTP', product_group: :source_code do
+ describe 'Git clone over HTTP', :reliable, product_group: :source_code do
let(:project) { create(:project, name: 'project-with-code', description: 'project for git clone tests') }
before do
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb
index 0b55ea8241e..fcf64a95675 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb
@@ -24,7 +24,7 @@ module QA
Page::Project::Tag::Show.perform do |show|
expect(show).to have_tag_name(tag_name)
expect(show).to have_tag_message(tag_message)
- expect(show).not_to have_element(:create_tag_button)
+ expect(show).not_to have_element('create-tag-button')
end
end
end
@@ -37,7 +37,7 @@ module QA
Page::Project::Tag::New.perform do |new_tag|
expect(new_tag).to have_content('You are not allowed to create this tag as it is protected.')
- expect(new_tag).to have_element(:create_tag_button)
+ expect(new_tag).to have_element('create-tag-button')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 76aba401aaa..96aad8c9957 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -14,7 +14,7 @@ module QA
end
it(
- 'push successful when the file size is under the limit',
+ 'push successful when the file size is under the limit', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347758'
) do
set_file_size_limit(5)
@@ -27,7 +27,7 @@ module QA
end
it(
- 'push fails when the file size is above the limit',
+ 'push fails when the file size is above the limit', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347759'
) do
set_file_size_limit(2)
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb
new file mode 100644
index 00000000000..314585fd9e1
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', :skip_live_env, product_group: :ide do
+ describe 'Add first file in Web IDE' do
+ let(:project) { create(:project, :with_readme, name: 'webide-create-file-project') }
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ Page::Project::Show.perform(&:open_web_ide!)
+ Page::Project::WebIDE::VSCode.perform(&:wait_for_ide_to_load)
+ end
+
+ context 'when a file with the same name already exists' do
+ let(:file_name) { 'README.md' }
+
+ it 'throws an error', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/432899' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.create_new_file(file_name)
+
+ expect(ide)
+ .to have_message("A file or folder README.md already exists at this location")
+ end
+ end
+ end
+
+ context 'when user adds a new file' do
+ let(:file_name) { 'first_file.txt' }
+
+ it 'shows successfully added and visible in project',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/432898' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.create_new_file(file_name)
+ ide.commit_and_push_to_existing_branch(file_name)
+
+ expect(ide).to have_message('Success! Your changes have been committed.')
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project|
+ expect(project).to have_file(file_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb
new file mode 100644
index 00000000000..7df73268c83
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', product_group: :ide do
+ describe 'Closing Web IDE' do
+ let(:file_name) { 'file.txt' }
+ let(:project) { create(:project, :with_readme, name: 'webide-close-with-unsaved-changes') }
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ Page::Project::Show.perform(&:open_web_ide!)
+ Page::Project::WebIDE::VSCode.perform(&:wait_for_ide_to_load)
+ end
+
+ it 'shows an alert when there are unsaved changes',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/411298' do
+ Page::Project::WebIDE::VSCode.perform do |ide|
+ ide.create_new_file(file_name)
+ ide.has_file?(file_name)
+ ide.close_ide_tab
+ expect do
+ ide.ide_tab_closed?
+ end.to raise_error(Selenium::WebDriver::Error::UnexpectedAlertOpenError, /unexpected alert open/)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
index c038a6d8a87..5240dec2a4a 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb
@@ -30,7 +30,7 @@ module QA
it "verifies it successfully uploads and commits to a MR" do
Page::Project::WebIDE::VSCode.perform do |ide|
ide.upload_file(file_path)
- ide.commit_and_push(file_name)
+ ide.commit_and_push_to_new_branch(file_name)
expect(ide).to have_message('Success! Your changes have been committed.')
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/add_file_template_spec.rb
deleted file mode 100644
index 18dd4d912d8..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/add_file_template_spec.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'Web IDE file templates' do
- include Runtime::Fixtures
-
- before(:all) do
- @project = create(:project,
- :with_readme,
- name: 'file-template-project',
- description: 'Add file templates via Web IDE')
- end
-
- templates = [
- {
- file_name: '.gitignore',
- name: 'Android',
- api_path: 'gitignores',
- api_key: 'Android',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347752'
- },
- {
- file_name: '.gitlab-ci.yml',
- name: 'Julia',
- api_path: 'gitlab_ci_ymls',
- api_key: 'Julia',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347753'
- },
- {
- file_name: 'Dockerfile',
- name: 'Python',
- api_path: 'dockerfiles',
- api_key: 'Python',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347750'
- },
- {
- file_name: 'LICENSE',
- name: 'Mozilla Public License 2.0',
- api_path: 'licenses',
- api_key: 'mpl-2.0',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347751'
- }
- ]
-
- templates.each do |template|
- it "user adds #{template[:file_name]} via file template #{template[:name]}", testcase: template[:testcase] do
- content = fetch_template_from_api(template[:api_path], template[:api_key])
-
- Flow::Login.sign_in
-
- @project.visit!
-
- Page::Project::Show.perform(&:open_web_ide!)
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- ide.create_new_file_from_template template[:file_name], template[:name]
-
- expect(ide.has_file?(template[:file_name])).to be_truthy
- expect(ide).to have_button('Undo')
- expect(ide).to have_normalized_ws_text(content[0..100])
-
- ide.rename_file(template[:file_name], "#{SecureRandom.hex(8)}/#{template[:file_name]}")
-
- ide.commit_changes
-
- expect(ide).to have_content(template[:file_name])
- expect(ide).to have_normalized_ws_text(content[0..100])
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/create_first_file_in_web_ide_spec.rb
deleted file mode 100644
index 8fe58078393..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/create_first_file_in_web_ide_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'First file using Web IDE' do
- let(:project) { create(:project, :with_readme, name: 'empty-project') }
- let(:file_name) { 'the very first file.txt' }
-
- before do
- Flow::Login.sign_in
- end
-
- it "creates the first file in an empty project via Web IDE", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347803' do
- project.visit!
- Page::Project::Show.perform(&:create_first_new_file!)
-
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- ide.create_first_file(file_name)
- ide.commit_changes
- end
-
- project.visit!
-
- Page::Project::Show.perform do |project|
- expect(project).to have_file(file_name)
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/link_to_line_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/link_to_line_in_web_ide_spec.rb
deleted file mode 100644
index 57c8945d5c4..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/link_to_line_in_web_ide_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'Link to line in Web IDE' do
- let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
- let(:project) { create(:project, template_name: 'express') }
-
- before do
- Flow::Login.sign_in
- end
-
- after do
- project.remove_via_api!
- end
-
- it 'can link to a specific line of code in Web IDE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347676' do
- project.visit!
-
- # Open Web IDE by using a keyboard shortcut
- Page::Project::Show.perform(&:open_web_ide_via_shortcut)
-
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- ide.select_file('app.js')
- @link = ide.link_line('26')
- end
-
- Flow::Login.sign_in(as: user)
-
- page.visit(@link)
-
- Page::Project::WebIDE::Edit.perform do |ide|
- expect(ide).to have_file('app.js')
- end
-
- expect(page.driver.current_url).to include('app.js/#L26')
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/open_web_ide_from_diff_tab_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/open_web_ide_from_diff_tab_spec.rb
deleted file mode 100644
index 9607077310e..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/open_web_ide_from_diff_tab_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'Open Web IDE from Diff Tab' do
- files = [
- {
- action: 'create',
- file_path: 'file1',
- content: 'test1'
- },
- {
- action: 'create',
- file_path: 'file2',
- content: 'test2'
- },
- {
- action: 'create',
- file_path: 'file3',
- content: 'test3'
- }
- ]
-
- let(:project) { create(:project, :with_readme) }
- let(:source) do
- create(:commit,
- project: project,
- branch: 'new-mr',
- start_branch: project.default_branch,
- commit_message: 'Add new files', actions: files)
- end
-
- let(:merge_request) do
- create(:merge_request, source: source, project: project, source_branch: 'new-mr', target_new_branch: false)
- end
-
- before do
- Flow::Login.sign_in
- merge_request.visit!
- end
-
- it 'opens and edits a multi-file merge request in Web IDE from Diff Tab', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347724' do
- Page::MergeRequest::Show.perform do |show|
- show.click_diffs_tab
- show.edit_file_in_web_ide('file1')
- end
-
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- files.each do |files|
- expect(ide).to have_file(files[:file_path])
- expect(ide).to have_file_content(files[:file_path], files[:content])
- end
-
- ide.delete_file('file1')
- ide.commit_changes
- end
-
- merge_request.visit!
-
- Page::MergeRequest::Show.perform do |show|
- show.click_diffs_tab
-
- expect(show).not_to have_file('file1')
- expect(show).to have_file('file2')
- expect(show).to have_file('file3')
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/review_merge_request_spec.rb
deleted file mode 100644
index ec41cf9b43a..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/review_merge_request_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'Review a merge request in Web IDE' do
- let(:new_file) { 'awesome_new_file.txt' }
- let(:original_text) { 'Text' }
- let(:review_text) { 'Reviewed ' }
- let(:project) { create(:project, :with_readme, name: 'review-mr-spec-project') }
-
- let(:merge_request) do
- create(:merge_request, file_name: new_file, file_content: original_text, project: project)
- end
-
- before do
- Flow::Login.sign_in
- merge_request.visit!
- end
-
- it 'opens and edits a merge request in Web IDE', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347786' do
- Page::MergeRequest::Show.perform do |show|
- show.click_open_in_web_ide
- end
-
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- ide.has_file?(new_file)
- ide.add_to_modified_content(review_text)
- ide.commit_changes
- end
-
- merge_request.visit!
-
- Page::MergeRequest::Show.perform do |show|
- show.click_diffs_tab
- expect(show).to have_content(review_text)
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb
deleted file mode 100644
index 42fcbf5352f..00000000000
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-# TODO: remove this test when coverage is replaced or deemed irrelevant
-module QA
- RSpec.describe 'Create', :skip_live_env, except: { job: 'review-qa-*' }, product_group: :ide do
- before do
- skip("Skipped but kept as reference. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115741#note_1330720944")
- end
-
- describe 'Git Server Hooks' do
- let(:file_path) { Runtime::Path.fixture('web_ide', 'README.md') }
-
- # Projects that have names that include pattern 'reject-prereceive' trigger a server hook on orchestrated env
- # that returns an error string using GL-HOOK-ERR
- let(:project) { create(:project, name: "project-reject-prereceive-#{SecureRandom.hex(8)}") }
-
- before do
- Flow::Login.sign_in
- project.visit!
- end
-
- context 'with custom error messages' do
- it 'renders preconfigured error message when user hook failed on commit in WebIDE',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/364751' do
- Page::Project::Show.perform(&:open_web_ide_via_shortcut)
- Page::Project::WebIDE::Edit.perform do |ide|
- ide.wait_until_ide_loads
- ide.upload_file(file_path)
- ide.commit_changes(wait_for_success: false)
- expect(ide).to have_text('Custom error message rejecting prereceive hook for projects with GL_PROJECT_PATH')
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
index e80c969a68d..9fbb5e3acb4 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb
@@ -40,6 +40,7 @@ module QA
before do
Flow::Login.sign_in
project.visit!
+ Support::Waiter.wait_until(message: 'Wait for pipeline creation') { project.pipelines.length == 1 }
# Navigate to Run Pipeline page
Page::Project::Menu.perform(&:go_to_pipelines)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb
index 9fe0f1c54b3..3b4ac0af0b1 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb
@@ -1,12 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Verify', :runner, product_group: :runner,
- quarantine: {
- only: { job: 'airgapped' },
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/390184',
- type: :stale
- } do
+ RSpec.describe 'Verify', :runner, product_group: :runner do
describe 'Group runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
@@ -21,7 +16,7 @@ module QA
end
it(
- 'user registers a new group runner', :reliable,
+ 'user registers a new group runner',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/388740'
) do
Flow::Login.sign_in
@@ -30,7 +25,9 @@ module QA
Page::Group::Menu.perform(&:go_to_runners)
- expect(page).to have_content(executor)
+ Support::Retrier.retry_on_exception(sleep_interval: 2, message: "Retry failed when looking for runner name") do
+ expect(page).to have_content(executor)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb
index 319b63291fd..66ea0cf913c 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/saas/pull_container_registry_image_spec.rb
@@ -9,7 +9,11 @@ module QA
end
it 'pulls an image from an existing repository',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412799' do
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/412799',
+ quarantine: {
+ type: :test_environment,
+ issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/435585"
+ } do
project = build(:project, path_with_namespace: 'gitlab-qa/container-registry-sanity').reload!
project.visit!
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 7c9c3869fdf..4d8a0a20961 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
@@ -29,7 +29,8 @@ module QA
package.remove_via_api!
end
- it 'publishes, installs, and deletes a Conan package', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348014' do
+ it 'publishes, installs, and deletes a Conan package',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348014' do
Flow::Login.sign_in
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
@@ -48,7 +49,7 @@ module QA
end
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
+ expect(job).to be_successful(timeout: 180)
end
Page::Project::Menu.perform(&:go_to_package_registry)
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 2163d73614d..99eb14fb376 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
@@ -42,7 +42,7 @@ module QA
end
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
+ expect(job).to be_successful(timeout: 180)
job.go_to_pipeline
end
@@ -52,7 +52,7 @@ module QA
end
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
+ expect(job).to be_successful(timeout: 180)
end
end
@@ -61,7 +61,8 @@ module QA
package.remove_via_api!
end
- it 'uploads a generic package and downloads it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348017' do
+ it 'uploads a generic package and downloads it', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348017' do
Page::Project::Menu.perform(&:go_to_package_registry)
Page::Project::Packages::Index.perform do |index|
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 91a25e68f00..0d027393150 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :object_storage, product_group: :package_registry do
+ RSpec.describe 'Package', :object_storage, :reliable, product_group: :package_registry do
describe 'Helm Registry', :external_api_calls do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
@@ -65,7 +65,7 @@ module QA
end
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
+ expect(job).to be_successful(timeout: 180)
end
Page::Project::Menu.perform(&:go_to_package_registry)
@@ -97,7 +97,7 @@ module QA
end
Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
+ expect(job).to be_successful(timeout: 180)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 66906c46e21..2bf00350093 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -3,7 +3,8 @@
module QA
RSpec.describe 'Release', product_group: :environments do
describe 'Deploy key creation' do
- it 'user adds a deploy key', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348023' do
+ it 'user adds a deploy key', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348023' do
Flow::Login.sign_in
key = Runtime::Key::RSA.new
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index f6bea2535a3..ae602c79a03 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,7 @@
require 'digest/sha1'
module QA
- RSpec.describe 'Release', :runner, product_group: :environments do
+ RSpec.describe 'Release', :runner, :reliable, product_group: :environments do
describe 'Git clone using a deploy key' do
let(:runner_name) { "qa-runner-#{SecureRandom.hex(4)}" }
let(:repository_location) { project.repository_ssh_location }
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index 109c153ea48..426f32fe4dc 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -3,7 +3,8 @@
module QA
RSpec.describe 'Release', product_group: :environments do
describe 'Deploy token creation' do
- it 'user adds a deploy token', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348028' do
+ it 'user adds a deploy token', :reliable,
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348028' do
Flow::Login.sign_in
deploy_token_name = 'deploy token name'
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb
index dff130a5793..192a08dc5ab 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb
@@ -22,7 +22,7 @@ module QA
end
context(
- 'when using HTTP endpoint integration',
+ 'when using HTTP endpoint integration', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/388469'
) do
before do
@@ -34,7 +34,7 @@ module QA
end
context(
- 'when using Prometheus integration',
+ 'when using Prometheus integration', :reliable,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/390123'
) do
before do
diff --git a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb
index af088d2978a..ca695768d56 100644
--- a/qa/qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb
+++ b/qa/qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb
@@ -40,7 +40,11 @@ module QA
context(
'when using Prometheus integration',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/393590'
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/393590',
+ quarantine: {
+ type: :flaky,
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/413220'
+ }
) do
include_context 'sends and resolves test alerts'
diff --git a/qa/qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb b/qa/qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb
index 28253c1f1df..313b2d828eb 100644
--- a/qa/qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb
@@ -15,9 +15,7 @@ module QA
Flow::Login.sign_in(as: user)
Page::Dashboard::Projects.perform do |projects|
- projects.filter_by_name(project.name)
-
- expect(projects).to have_project_with_access_role(project.name, 'Developer')
+ expect(projects).to have_filtered_project_with_access_role(project.name, 'Developer')
end
project.visit!
diff --git a/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb b/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb
index 28d05fd58c0..35e3ea1411b 100644
--- a/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb
@@ -25,9 +25,7 @@ module QA
it "has owner role and permissions", testcase: testcase do
Page::Dashboard::Projects.perform do |projects|
- projects.filter_by_name(project.name)
-
- expect(projects).to have_project_with_access_role(project.name, 'Owner')
+ expect(projects).to have_filtered_project_with_access_role(project.name, 'Owner')
end
issue.visit!
@@ -52,9 +50,7 @@ module QA
it "has maintainer role without owner permissions", testcase: testcase do
Page::Dashboard::Projects.perform do |projects|
- projects.filter_by_name(project.name)
-
- expect(projects).to have_project_with_access_role(project.name, 'Maintainer')
+ expect(projects).to have_filtered_project_with_access_role(project.name, 'Maintainer')
end
issue.visit!
diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
index b6ea046fd59..56d1de022fb 100644
--- a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
+++ b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
@@ -71,19 +71,24 @@ module QA
imported_group.import_details.sum([]) { |details| details[:failures] }
end
- let(:cleanup!) {} # rubocop:disable Lint/EmptyBlock
-
def expect_group_import_finished_successfully
imported_group # trigger import
- status = nil
- Support::Retrier.retry_until(**import_wait_duration, raise_on_failure: false) do
- status = imported_group.import_status
- %w[finished failed].include?(status)
- end
-
- # finished status means success, all other statuses are considered to fail the test
- expect(status).to eq('finished'), "Expected import to finish successfully, but status was: #{status}"
+ import_status = -> {
+ status = Support::Retrier.retry_on_exception(
+ sleep_interval: 1,
+ log: false,
+ message: "Fetching import status"
+ ) do
+ imported_group.import_status
+ end
+ # fail fast if import explicitly failed, we don't test negative scenarios where we expect failed status
+ raise "Import of '#{imported_group.full_path}' failed!" if status == 'failed'
+
+ status
+ }
+
+ expect(import_status).to eventually_eq('finished').within(**import_wait_duration)
end
before do
@@ -98,10 +103,7 @@ module QA
# Log failures for easier debugging
Runtime::Logger.error("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
rescue StandardError
- # rescue when import did not happen at all and checking import failues will raise an error
- ensure
- # make sure cleanup runs last
- cleanup!
+ # rescue when import did not happen at all and checking import failures will raise an error
end
def enable_bulk_import(api_client)
diff --git a/qa/qa/specs/helpers/feature_setup.rb b/qa/qa/specs/helpers/feature_setup.rb
new file mode 100644
index 00000000000..fd1e84a5691
--- /dev/null
+++ b/qa/qa/specs/helpers/feature_setup.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+module QA
+ module Specs
+ module Helpers
+ class FeatureSetup
+ class << self
+ # Set up feature flags
+ #
+ # @return [void]
+ def configure!
+ configure_rspec
+ end
+
+ private
+
+ # Add global hooks to perform feature flag changes
+ #
+ # @return [void]
+ def configure_rspec
+ setup = new
+
+ ::RSpec.configure do |config|
+ config.before(:suite) { setup.run_before }
+ config.after(:suite) { setup.run_after }
+ end
+ end
+ end
+
+ FF_PATTERN = /[a-z_]+=(enabled|disabled)/
+
+ private_class_method :new
+
+ def initialize
+ @options = Support::GlobalOptions.get
+ @enable_feature = options[:enable_feature]
+ @disable_feature = options[:disable_feature]
+ end
+
+ # Run feature setup before suite
+ #
+ # @return [void]
+ def run_before
+ set_feature_flags
+
+ enable_features
+ disable_features
+ end
+
+ # Restore feature state after suite
+ #
+ # @return [void]
+ def run_after
+ Runtime::Feature.disable(enable_feature) if enable_feature && !enabled
+ Runtime::Feature.enable(disable_feature) if disable_feature && !disabled
+ end
+
+ private
+
+ delegate :logger, to: Runtime::Logger
+
+ attr_reader :options, :enable_feature, :disable_feature, :enabled, :disabled
+
+ # Feature flags to set
+ #
+ # @return [<String, nil>]
+ def feature_flags
+ return @feature_flags if defined?(@feature_flags)
+
+ @feature_flags ||= options[:set_feature_flags] || feature_flags_from_env
+ end
+
+ # Fetch feature flags from environment variable
+ #
+ # @return [<Hash, nil>]
+ def feature_flags_from_env
+ ff = ENV["QA_FEATURE_FLAGS"]
+ return if ff.blank?
+
+ ff.split(",").each_with_object({}) do |flag, hash|
+ unless flag.match?(FF_PATTERN)
+ error_msg = "'#{flag}' in QA_FEATURE_FLAGS environment variable doesn't match pattern '#{FF_PATTERN}'"
+ next logger.error(error_msg)
+ end
+
+ name, value = flag.split("=")
+ hash[name] = value
+ end
+ end
+
+ # Update group of feature flags
+ #
+ # @return [void]
+ def set_feature_flags
+ return unless feature_flags
+
+ Runtime::Feature.set(feature_flags)
+ end
+
+ # Enable features
+ #
+ # @return [void]
+ def enable_features
+ return unless enable_feature
+
+ @enabled = Runtime::Feature.enabled?(enable_feature)
+ return if @enabled
+
+ Runtime::Feature.enable(enable_feature)
+ end
+
+ # Disable features
+ #
+ # @return [void]
+ def disable_features
+ return unless disable_feature
+
+ @disabled = !Runtime::Feature.enabled?(disable_feature)
+ return if @disabled
+
+ Runtime::Feature.disable(disable_feature)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb
index 965e3c2f88c..d4295ce72e7 100644
--- a/qa/qa/specs/spec_helper.rb
+++ b/qa/qa/specs/spec_helper.rb
@@ -12,11 +12,12 @@ QA::Specs::QaDeprecationToolkitEnv.configure!
Knapsack::Adapters::RSpecAdapter.bind if QA::Runtime::Env.knapsack?
+# TODO: move all classes that perform rspec configuration under spec/helpers
QA::Support::GitlabAddress.define_gitlab_address_attribute!
-QA::Runtime::Browser.configure! unless QA::Runtime::Env.dry_run
+QA::Runtime::Browser.configure!
+QA::Specs::Helpers::FeatureSetup.configure!
QA::Runtime::AllureReport.configure!
QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
-QA::Support::KnapsackReport.configure!
QA::Service::DockerRun::Video.configure!
# Enable zero monkey patching mode before loading any other RSpec code.
@@ -49,6 +50,11 @@ RSpec.configure do |config|
Thread.current[:browser_ui_fabrication] = 0
end
+ config.prepend_before(:suite) do
+ # Perform before hooks at the very start of the test run
+ QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run
+ end
+
config.before(:suite) do
FactoryBot.find_definitions
end
@@ -122,7 +128,7 @@ RSpec.configure do |config|
# show exception that triggers a retry if verbose_retry is set to true
config.display_try_failure_messages = true
- non_quarantine_retries = QA::Runtime::Env.ci_project_name =~ /staging|canary|production/ ? 3 : 2
+ non_quarantine_retries = QA::Runtime::Env.ci_project_name.match?(/staging|canary|production/) ? 3 : 2
config.around do |example|
quarantine = example.metadata[:quarantine]
different_quarantine_context = QA::Specs::Helpers::Quarantine.quarantined_different_context?(quarantine)
@@ -136,5 +142,5 @@ RSpec.configure do |config|
end
end
-Dir[::File.join(__dir__, "features/shared_examples/**/*.rb")].sort.each { |f| require f }
-Dir[::File.join(__dir__, "features/shared_contexts/**/*.rb")].sort.each { |f| require f }
+Dir[::File.join(__dir__, "features/shared_examples/**/*.rb")].each { |f| require f }
+Dir[::File.join(__dir__, "features/shared_contexts/**/*.rb")].each { |f| require f }
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index f0a73391d5d..6431511e6c4 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -119,7 +119,7 @@ module QA
def with_retry_on_too_many_requests
response = nil
- Support::Retrier.retry_until(log: false) do
+ Support::Retrier.retry_until(log: false, message: "Retrying upon receiving 429 HTTP status") do
response = yield
if response.code == HTTP_STATUS_TOO_MANY_REQUESTS
@@ -159,7 +159,7 @@ module QA
loop do
response = if attempts > 0
- Retrier.retry_on_exception(max_attempts: attempts, log: false) do
+ Retrier.retry_on_exception(max_attempts: attempts, log: false, sleep_interval: 1) do
get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
end
else
diff --git a/qa/qa/support/global_options.rb b/qa/qa/support/global_options.rb
new file mode 100644
index 00000000000..1069d9e720d
--- /dev/null
+++ b/qa/qa/support/global_options.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ # Global options for test run
+ #
+ module Support
+ class GlobalOptions
+ class << self
+ # Get global cli options
+ #
+ # @return [Hash]
+ def get
+ @options ||= {}
+ end
+
+ # Set global cli options
+ #
+ # @param [Hash] options
+ # @return [Hash]
+ def set(options)
+ @options = options
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/helpers/project.rb b/qa/qa/support/helpers/project.rb
new file mode 100644
index 00000000000..8909d8338f4
--- /dev/null
+++ b/qa/qa/support/helpers/project.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ module Helpers
+ module Project
+ def wait_until_project_is_ready(project)
+ # Repository can take a short time to become ready after project is created
+ Support::Retrier.retry_on_exception(sleep_interval: 5) do
+ create(:commit, project: project, commit_message: 'Add new file', actions: [
+ { action: 'create', file_path: 'new_file', content: '# This is a new file' }
+ ])
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index d7bf8b76924..836ff33f543 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -58,16 +58,6 @@ module QA
File.write(selective_path, filtered_timed_specs.to_json)
end
- # Add '-selective-parallel' suffix to report name
- #
- # @return [String]
- def selective_path
- extension = File.extname(report_path)
- directory = File.dirname(report_path)
- file_name = File.basename(report_path, extension)
- File.join(directory, "#{file_name}-selective-parallel#{extension}")
- end
-
# Rename and move new regenerated report to a separate folder used to indicate report name
#
# @return [void]
@@ -103,7 +93,7 @@ module QA
report = jsons
.map { |json| JSON.parse(File.read(json)) }
.reduce({}, :merge)
- .sort_by { |k, v| v } # sort report by execution time
+ .sort_by { |_k, v| v } # sort report by execution time
.to_h
next logger.warn("Knapsack generated empty report for '#{name}', skipping upload!") if report.empty?
@@ -188,6 +178,17 @@ module QA
{ google_json_key_string: json_key }
end
+
+ # Add '-selective-parallel' suffix to report name
+ #
+ # @return [String]
+ def selective_path
+ extension = File.extname(report_path)
+ directory = File.dirname(report_path)
+ file_name = File.basename(report_path, extension)
+
+ File.join(directory, "#{file_name}-selective-parallel#{extension}")
+ end
end
end
end
diff --git a/qa/qa/support/run.rb b/qa/qa/support/run.rb
index da82c09462d..cde8a0dba21 100644
--- a/qa/qa/support/run.rb
+++ b/qa/qa/support/run.rb
@@ -21,11 +21,11 @@ module QA
end
end
- def run(command_str, env: [], max_attempts: 1, log_prefix: '')
+ def run(command_str, env: [], max_attempts: 1, sleep_internal: 0, log_prefix: '')
command = [*env, command_str, '2>&1'].compact.join(' ')
result = nil
- repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
+ repeat_until(max_attempts: max_attempts, sleep_interval: sleep_internal, raise_on_failure: false) do
Runtime::Logger.debug "#{log_prefix}pwd=[#{Dir.pwd}], command=[#{command}]"
output, status = Open3.capture2e(command)
output.chomp!
diff --git a/qa/qa/support/ssh.rb b/qa/qa/support/ssh.rb
index eebe5e65504..b2fbddce87d 100644
--- a/qa/qa/support/ssh.rb
+++ b/qa/qa/support/ssh.rb
@@ -25,7 +25,7 @@ module QA
File.binwrite(private_key_file, key.private_key)
File.chmod(0700, private_key_file)
- keyscan_params = ['-H']
+ keyscan_params = ['-T 60 -H']
keyscan_params << "-p #{uri_port}" if uri_port
keyscan_params << uri.host
diff --git a/qa/qa/tools/ci/qa_changes.rb b/qa/qa/tools/ci/qa_changes.rb
index 91c7760933f..31de7aec33a 100644
--- a/qa/qa/tools/ci/qa_changes.rb
+++ b/qa/qa/tools/ci/qa_changes.rb
@@ -126,6 +126,7 @@ module QA
return unless devops_stage
spec_dirs = stage_specs(devops_stage)
+ return if spec_dirs.empty?
grp_name = group_name_from_mr_labels
return spec_dirs if grp_name.nil?
@@ -155,7 +156,7 @@ module QA
# @param [Array<String>] devops_stages
# @return [Array]
def stage_specs(*devops_stages)
- Dir.glob("qa/specs/**/*/").select { |dir| dir =~ %r{\d+_(#{devops_stages.join('|')})/$} }
+ Dir.glob("qa/specs/features/**/*/").select { |dir| dir =~ %r{\d+_(#{devops_stages.join('|')})/$} }
end
end
end
diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb
index a0933d07166..7569095b56f 100644
--- a/qa/qa/tools/reliable_report.rb
+++ b/qa/qa/tools/reliable_report.rb
@@ -31,6 +31,7 @@ module QA
PROJECT_ID = 278964
FEATURES_DIR = 'https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/specs/features/'
+ # @param [Integer] range amount of days for results range
def initialize(range)
@range = range.to_i
@slack_channel = "#quality-reports"
@@ -59,14 +60,14 @@ module QA
#
# @return [void]
def print_report
- puts "#{stable_summary_table}\n\n"
+ puts "#{summary_table(stable: true)}\n\n"
puts "Total amount: #{stable_test_runs.sum { |_k, v| v.count }}\n\n"
- stable_results_tables.each { |stage, table| puts "#{table}\n\n" }
+ print_results(stable_results_tables)
return puts("No unstable reliable tests present!".colorize(:yellow)) if unstable_reliable_test_runs.empty?
- puts "#{unstable_summary_table}\n\n"
+ puts "#{summary_table(stable: false)}\n\n"
puts "Total amount: #{unstable_reliable_test_runs.sum { |_k, v| v.count }}\n\n"
- unstable_reliable_results_tables.each { |stage, table| puts "#{table}\n\n" }
+ print_results(unstable_reliable_results_tables)
end
# Create report issue
@@ -89,8 +90,8 @@ module QA
notifier.post(
icon_emoji: ":tanuki-protect:",
text: <<~TEXT
- ```#{stable_summary_table}```
- ```#{unstable_summary_table}```
+ ```#{summary_table(stable: true)}```
+ ```#{summary_table(stable: false)}```
#{web_url}
TEXT
@@ -173,39 +174,30 @@ module QA
issue = []
issue << "[[_TOC_]]"
issue << "# Candidates for promotion to reliable #{execution_interval}"
- issue << "Total amount: **#{stable_test_runs.sum { |_k, v| v.count }}**"
- issue << stable_summary_table(markdown: true).to_s
+ issue << "Total amount: **#{test_count(stable_test_runs)}**"
+ issue << summary_table(markdown: true, stable: true).to_s
issue << results_markdown(:stable)
return issue.join("\n\n") if unstable_reliable_test_runs.empty?
issue << "# Reliable specs with failures #{execution_interval}"
- issue << "Total amount: **#{unstable_reliable_test_runs.sum { |_k, v| v.count }}**"
- issue << unstable_summary_table(markdown: true).to_s
+ issue << "Total amount: **#{test_count(unstable_reliable_test_runs)}**"
+ issue << summary_table(markdown: true, stable: false).to_s
issue << results_markdown(:unstable)
issue.join("\n\n")
end
- # Stable spec summary table
- #
- # @param [Boolean] markdown
- # @return [Terminal::Table]
- def stable_summary_table(markdown: false)
- terminal_table(
- rows: stable_test_runs.map { |stage, specs| [stage, specs.length] },
- title: "Stable spec summary for past #{range} days".ljust(50),
- headings: %w[STAGE COUNT],
- markdown: markdown
- )
- end
-
- # Unstable reliable summary table
+ # Spec summary table
#
# @param [Boolean] markdown
+ # @param [Boolean] stable
# @return [Terminal::Table]
- def unstable_summary_table(markdown: false)
+ def summary_table(markdown: false, stable: true)
+ test_runs = stable ? stable_test_runs : unstable_reliable_test_runs
terminal_table(
- rows: unstable_reliable_test_runs.map { |stage, specs| [stage, specs.length] },
- title: "Unstable spec summary for past #{range} days".ljust(50),
+ rows: test_runs.map do |stage, stage_specs|
+ [stage, stage_specs.sum { |_k, group_specs| group_specs.length }]
+ end,
+ title: "#{stable ? 'Stable' : 'Unstable'} spec summary for past #{range} days".ljust(50),
headings: %w[STAGE COUNT],
markdown: markdown
)
@@ -233,41 +225,51 @@ module QA
# @return [String]
def results_markdown(type)
runs = type == :stable ? stable_test_runs : unstable_reliable_test_runs
- results_tables(type, markdown: true).map do |stage, table|
- <<~STAGE.strip
- ## #{stage} (#{runs[stage].count})
+ results_tables(type, markdown: true).map do |stage, group_tables|
+ markdown = "## #{stage.capitalize} (#{runs[stage].sum { |_k, group_runs| group_runs.count }})\n\n"
- <details>
- <summary>Executions table</summary>
+ markdown << group_tables.map { |product_group, table| group_results_markdown(product_group, table) }.join
+ end.join("\n\n")
+ end
- #{table}
+ # Markdown formatted group results table
+ #
+ # @param [String] product_group
+ # @param [Terminal::Table] table
+ # @return [String]
+ def group_results_markdown(product_group, table)
+ <<~MARKDOWN.chomp
+ <details>
+ <summary>Executions table ~"group::#{product_group.tr('_', ' ')}" (#{table.rows.size})</summary>
- </details>
- STAGE
- end.join("\n\n")
+ #{table}
+
+ </details>
+ MARKDOWN
end
# Results table
#
# @param [Symbol] type result type - :stable, :unstable
# @param [Boolean] markdown
- # @return [Hash<Symbol, Terminal::Table>]
+ # @return [Hash<String, Hash<String, Terminal::Table>>] grouped by stage and product_group
def results_tables(type, markdown: false)
(type == :stable ? stable_test_runs : unstable_reliable_test_runs).to_h do |stage, specs|
- headings = ["name", "runs", "failures", "failure rate"]
-
- [stage, terminal_table(
- title: "Top #{type} specs in '#{stage}' stage for past #{range} days",
- headings: headings.map(&:upcase),
- markdown: markdown,
- rows: specs.map do |k, v|
- [
- name_column(name: k, file: v[:file], link: v[:link],
- exceptions_and_job_urls: v[:exceptions_and_job_urls], markdown: markdown),
- *table_params(v.values)
- ]
- end
- )]
+ headings = ['NAME', 'RUNS', 'FAILURES', 'FAILURE RATE'].freeze
+ [stage, specs.transform_values do |group_specs|
+ terminal_table(
+ title: "Top #{type} specs in '#{stage}::#{specs.key(group_specs)}' group for past #{range} days",
+ headings: headings,
+ markdown: markdown,
+ rows: group_specs.map do |name, result|
+ [
+ name_column(name: name, file: result[:file], link: result[:link],
+ exceptions_and_related_urls: result[:exceptions_and_related_urls], markdown: markdown),
+ *table_params(result.values)
+ ]
+ end
+ )
+ end]
end
end
@@ -276,14 +278,14 @@ module QA
# @return [Hash]
def stable_test_runs
@top_stable ||= begin
- stable_specs = test_runs(reliable: false).transform_values do |specs|
- specs
- .reject { |k, v| v[:failure_rate] != 0 }
- .sort_by { |k, v| -v[:runs] }
- .to_h
+ stable_specs = test_runs(reliable: false).each do |stage, stage_specs|
+ stage_specs.transform_values! do |group_specs|
+ group_specs.reject { |k, v| v[:failure_rate] != 0 }
+ .sort_by { |k, v| -v[:runs] }
+ .to_h
+ end
end
-
- stable_specs.reject { |k, v| v.empty? }
+ stable_specs.transform_values { |v| v.reject { |_, v| v.empty? } }.reject { |_, v| v.empty? }
end
end
@@ -292,14 +294,26 @@ module QA
# @return [Hash]
def unstable_reliable_test_runs
@top_unstable_reliable ||= begin
- unstable = test_runs(reliable: true).transform_values do |specs|
- specs
- .reject { |k, v| v[:failure_rate] == 0 }
- .sort_by { |k, v| -v[:failure_rate] }
- .to_h
+ unstable = test_runs(reliable: true).each do |_stage, stage_specs|
+ stage_specs.transform_values! do |group_specs|
+ group_specs.reject { |_, v| v[:failure_rate] == 0 }
+ .sort_by { |_, v| -v[:failure_rate] }
+ .to_h
+ end
end
+ unstable.transform_values { |v| v.reject { |_, v| v.empty? } }.reject { |_, v| v.empty? }
+ end
+ end
+
+ def print_results(results)
+ results.each do |_stage, stage_results|
+ stage_results.each_value { |group_results_table| puts "#{group_results_table}\n\n" }
+ end
+ end
- unstable.reject { |k, v| v.empty? }
+ def test_count(test_runs)
+ test_runs.sum do |_stage, stage_results|
+ stage_results.sum { |_product_group, group_results| group_results.count }
end
end
@@ -332,12 +346,13 @@ module QA
# @param [String] name
# @param [String] file
# @param [String] link
- # @param [Hash] exceptions_and_job_urls
+ # @param [Hash] exceptions_and_related_urls
# @param [Boolean] markdown
# @return [String]
- def name_column(name:, file:, link:, exceptions_and_job_urls:, markdown: false)
+ def name_column(name:, file:, link:, exceptions_and_related_urls:, markdown: false)
if markdown
- return "**Name**: #{name}<br>**File**: [#{file}](#{link})#{exceptions_markdown(exceptions_and_job_urls)}"
+ return "**Name**: #{name}<br>**File**: " \
+ "[#{file}](#{link})#{exceptions_markdown(exceptions_and_related_urls)}"
end
wrapped_name = name.length > 150 ? "#{name} ".scan(/.{1,150} /).map(&:strip).join("\n") : name
@@ -346,13 +361,13 @@ module QA
# Formatted exceptions with link to job url
#
- # @param [Hash] exceptions_and_job_urls
+ # @param [Hash] exceptions_and_related_urls
# @return [String]
- def exceptions_markdown(exceptions_and_job_urls)
- return '' if exceptions_and_job_urls.empty?
+ def exceptions_markdown(exceptions_and_related_urls)
+ return '' if exceptions_and_related_urls.empty?
- "<br>**Exceptions**:#{exceptions_and_job_urls.keys.map do |e|
- "<br>- [`#{e.truncate(250).tr('`', "'")}`](#{exceptions_and_job_urls[e]})"
+ "<br>**Exceptions**:#{exceptions_and_related_urls.keys.map do |e|
+ "<br>- [`#{e.truncate(250).tr('`', "'")}`](#{exceptions_and_related_urls[e]})"
end.join('')}"
end
@@ -368,16 +383,14 @@ module QA
all_runs.each_with_object(Hash.new { |hsh, key| hsh[key] = {} }) do |table, result|
records = table.records.sort_by { |record| record.values["_time"] }
- # skip specs that executed less time than defined by range or stopped executing before report date
- # offset 1 day due to how schedulers are configured and first run can be 1 day later
- next if (Date.today - Date.parse(records.first.values["_time"])).to_i < (range - 1)
- next if (Date.today - Date.parse(records.last.values["_time"])).to_i > 1
+ next if within_execution_range(records.first.values["_time"], records.last.values["_time"])
last_record = records.last.values
name = last_record["name"]
file = last_record["file_path"].split("/").last
link = FEATURES_DIR + last_record["file_path"]
stage = last_record["stage"] || "unknown"
+ product_group = last_record["product_group"] || "unknown"
runs = records.count
@@ -387,19 +400,13 @@ module QA
failure_rate = (failed.to_f / runs) * 100
- records_with_exception = records.reject { |r| !r.values["failure_exception"] }
-
- # Since exception is the key in the below hash, only one instance of an occurrence is kept
- exceptions_and_job_urls = records_with_exception.to_h do |r|
- [r.values["failure_exception"], r.values["job_url"]]
- end
-
- result[stage][name] = {
+ result[stage][product_group] ||= {}
+ result[stage][product_group][name] = {
file: file,
link: link,
runs: runs,
failed: failed,
- exceptions_and_job_urls: exceptions_and_job_urls,
+ exceptions_and_related_urls: exceptions_and_related_urls(records),
failure_rate: failure_rate == 0 ? failure_rate.round(0) : failure_rate.round(2)
}
end
@@ -407,6 +414,19 @@ module QA
# rubocop:enable Metrics/AbcSize
+ # Return hash of exceptions as key and failure_issue or job_url urls as value
+ #
+ # @param [Array<InfluxDB2::FluxRecord>] records
+ # @return [Hash]
+ def exceptions_and_related_urls(records)
+ records_with_exception = records.reject { |r| !r.values["failure_exception"] }
+
+ # Since exception is the key in the below hash, only one instance of an occurrence is kept
+ records_with_exception.to_h do |r|
+ [r.values["failure_exception"], r.values["failure_issue"] || r.values["job_url"]]
+ end
+ end
+
# Check if failure is allowed
#
# @param [String] failure_exception
@@ -415,6 +435,16 @@ module QA
ALLOWED_EXCEPTION_PATTERNS.any? { |pattern| pattern.match?(failure_exception) }
end
+ # Returns true if first_time is before our range, or if last_time is before report date
+ # offset 1 day due to how schedulers are configured and first run can be 1 day later
+ #
+ # @param [String] first_time
+ # @param [String] last_time
+ # @return [Boolean]
+ def within_execution_range(first_time, last_time)
+ (Date.today - Date.parse(first_time)).to_i < (range - 1) || (Date.today - Date.parse(last_time)).to_i > 1
+ end
+
# Flux query
#
# @param [Boolean] reliable
@@ -443,7 +473,8 @@ module QA
)
|> filter(fn: (r) => r["_field"] == "job_url" or
r["_field"] == "failure_exception" or
- r["_field"] == "id"
+ r["_field"] == "id" or
+ r["_field"] == "failure_issue"
)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> group(columns: ["name"])
diff --git a/qa/qa/vendor/snowplow/product_analytics/event.rb b/qa/qa/vendor/snowplow/product_analytics/event.rb
new file mode 100644
index 00000000000..5d2c44b0122
--- /dev/null
+++ b/qa/qa/vendor/snowplow/product_analytics/event.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Snowplow
+ module ProductAnalytics
+ class Event
+ include Scenario::Actable
+ include Support::API
+
+ def send(sdk_host, payload)
+ response = post(
+ "#{sdk_host}/com.snowplowanalytics.snowplow/tp2",
+ payload,
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ )
+
+ log("Sending snowplow event to #{sdk_host}. Response: #{response.code} #{response.body}")
+ end
+
+ def build_payload(sdk_app_id)
+ time_now = (Time.now.to_f * 1000).to_i
+ payload_hash = JSON.parse(template_payload)
+ payload_hash["data"][0]["aid"] = sdk_app_id
+ payload_hash["data"][0]["ue_px"] = ue_px
+ payload_hash["data"][0]["cx"] = cx
+ payload_hash["data"][0]["dtm"] = time_now.to_s
+ payload_hash["data"][0]["stm"] = (time_now + 1).to_s
+ payload_hash.to_json
+ end
+
+ private
+
+ # Log message
+ #
+ # @param [String] msg
+ # @param [Symbol] level
+ # @return [void]
+ def log(msg, level = :info)
+ QA::Runtime::Logger.public_send(level, msg)
+ end
+
+ def template_payload
+ '{"schema":"iglu:com.snowplowanalytics.snowplow/payload_data/jsonschema/1-0-4",
+ "data":[{
+ "e":"ue",
+ "eid":"12345678-1234-1234-1234-123456789012",
+ "tv":"js-3.12.0",
+ "tna":"gitlab",
+ "aid":"placeholder",
+ "p":"web",
+ "cookie":"1",
+ "cs":"UTF-8",
+ "lang":"en-US",
+ "res":"1728x1117",
+ "cd":"30",
+ "dtm":"placeholder",
+ "vp":"1728x462",
+ "ds":"1728x462",
+ "vid":"7",
+ "sid":"12345678-1234-1234-1234-123456789012",
+ "duid":"12345678-1234-1234-1234-123456789012",
+ "url":"http://localhost:8080/",
+ "ue_px":"placeholder",
+ "cx":"placeholder",
+ "stm":"placeholder"}
+ ]}'
+ end
+
+ def ue_px
+ Base64.encode64('{"schema":"iglu:com.snowplowanalytics.snowplow/unstruct_event/jsonschema/1-0-0",
+ "data":{
+ "schema":"iglu:com.gitlab/custom_event/jsonschema/1-0-0",
+ "data":{"name":"custom_event","props":{"key1":"value1"}}
+ }
+ }'
+ )
+ end
+
+ def cx
+ time_now = (Time.now.to_f * 1000).to_i
+ payload_hash = JSON.parse(cx_template_payload)
+ payload_hash = cx_set_base_timings(time_now, payload_hash)
+ payload_hash = cx_set_request_timings(time_now, payload_hash)
+ payload_hash = cx_set_dom_timings(time_now, payload_hash)
+ payload_hash = cx_set_event_load_timings(time_now, payload_hash)
+ payload = payload_hash.to_json
+
+ Base64.encode64(payload)
+ end
+
+ def cx_set_base_timings(time, payload_hash)
+ payload_hash["data"][2]["data"]["navigationStart"] = time
+ payload_hash["data"][2]["data"]["fetchStart"] = time
+ payload_hash["data"][2]["data"]["domainLookupStart"] = time
+ payload_hash["data"][2]["data"]["domainLookupEnd"] = time
+ payload_hash["data"][2]["data"]["connectStart"] = time
+ payload_hash["data"][2]["data"]["connectEnd"] = time
+ payload_hash
+ end
+
+ def cx_set_request_timings(time, payload_hash)
+ payload_hash["data"][2]["data"]["requestStart"] = time + 10
+ payload_hash["data"][2]["data"]["responseStart"] = time + 20
+ payload_hash["data"][2]["data"]["responseEnd"] = time + 20
+ payload_hash
+ end
+
+ def cx_set_dom_timings(time, payload_hash)
+ payload_hash["data"][2]["data"]["domLoading"] = time + 30
+ payload_hash["data"][2]["data"]["domInteractive"] = time + 50
+ payload_hash["data"][2]["data"]["domContentLoadedEventStart"] = time + 50
+ payload_hash["data"][2]["data"]["domContentLoadedEventEnd"] = time + 52
+ payload_hash["data"][2]["data"]["domComplete"] = time + 52
+ payload_hash
+ end
+
+ def cx_set_event_load_timings(time, payload_hash)
+ payload_hash["data"][2]["data"]["loadEventStart"] = time + 52
+ payload_hash["data"][2]["data"]["loadEventEnd"] = time + 52
+ payload_hash
+ end
+
+ def cx_template_payload
+ '{"schema":"iglu:com.snowplowanalytics.snowplow/contexts/jsonschema/1-0-0",
+ "data":[
+ {"schema":"iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0",
+ "data":{"id":"12345678-1234-1234-1234-123456789123"}
+ },
+ {"schema":"iglu:com.snowplowanalytics.snowplow/browser_context/jsonschema/1-0-0",
+ "data":{"viewport":"1687x417",
+ "documentSize":"1687x417",
+ "resolution":"1728x1117",
+ "colorDepth":30,
+ "devicePixelRatio":2,
+ "cookiesEnabled":true,
+ "online":true,
+ "browserLanguage":"en-US",
+ "documentLanguage":"en",
+ "webdriver":false,
+ "hardwareConcurrency":12,
+ "tabId":"12345678-1234-1234-1234-123456789123"}
+ },
+ {"schema":"iglu:org.w3/PerformanceTiming/jsonschema/1-0-0",
+ "data":{"navigationStart":1699963871632,
+ "redirectStart":0,
+ "redirectEnd":0,
+ "fetchStart":1699963871632,
+ "domainLookupStart":1699963871632,
+ "domainLookupEnd":1699963871632,
+ "connectStart":1699963871632,
+ "secureConnectionStart":0,
+ "connectEnd":1699963871632,
+ "requestStart":1699963871642,
+ "responseStart":1699963871653,
+ "responseEnd":1699963871653,
+ "unloadEventStart":0,
+ "unloadEventEnd":0,
+ "domLoading":1699963871660,
+ "domInteractive":1699963871723,
+ "domContentLoadedEventStart":1699963871723,
+ "domContentLoadedEventEnd":1699963871725,
+ "domComplete":1699963871725,
+ "loadEventStart":1699963871725,
+ "loadEventEnd":1699963871725}
+ }
+ ]
+ }'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json b/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json
index adf506c9d30..0b0d0f7cee0 100644
--- a/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json
+++ b/qa/spec/fixtures/knapsack_report/instance-selective-parallel.json
@@ -1,5 +1,4 @@
{
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 18.85673829499956,
- "qa/specs/features/api/3_create/repository/files_spec.rb": 3.180753622999873,
- "qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb": 0.010764157999801682
+ "qa/specs/features/api/3_create/repository/files_spec.rb": 3.180753622999873
}
diff --git a/qa/spec/fixtures/knapsack_report/instance.json b/qa/spec/fixtures/knapsack_report/instance.json
index 3d659bc53fb..f28bf78e890 100644
--- a/qa/spec/fixtures/knapsack_report/instance.json
+++ b/qa/spec/fixtures/knapsack_report/instance.json
@@ -1,7 +1,6 @@
{
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 18.85673829499956,
"qa/specs/features/api/3_create/repository/files_spec.rb": 3.180753622999873,
- "qa/specs/features/browser_ui/3_create/web_ide_old/server_hooks_custom_error_message_spec.rb": 0.010764157999801682,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.2808277129997805,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 4.882168451000325
}
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index 513e807dd99..c2442db9ec9 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe QA::Git::Repository do
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
let(:env_vars) { [%q(HOME="temp")] }
let(:extra_env_vars) { [] }
- let(:run_params) { { env: env_vars + extra_env_vars, log_prefix: "Git: " } }
+ let(:run_params) { { env: env_vars + extra_env_vars, sleep_internal: 10, log_prefix: "Git: " } }
let(:repository) do
described_class.new.tap do |r|
r.uri = repo_uri
diff --git a/qa/spec/runtime/path_spec.rb b/qa/spec/runtime/path_spec.rb
new file mode 100644
index 00000000000..276522b3276
--- /dev/null
+++ b/qa/spec/runtime/path_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.describe QA::Runtime::Path do
+ describe '.qa_root' do
+ it 'returns the fully-qualified path to the QA directory' do
+ expect(described_class.qa_root).to eq(File.expand_path('../../', __dir__))
+ end
+ end
+
+ describe '.fixtures_path' do
+ it 'returns the fully-qualified path to the fixtures directory' do
+ expect(described_class.fixtures_path).to eq(File.expand_path('../../qa/fixtures', __dir__))
+ end
+ end
+
+ describe '.fixture' do
+ it 'returns the fully-qualified path to a fixture file' do
+ expect(described_class.fixture('foo', 'bar')).to eq(File.expand_path('../../qa/fixtures/foo/bar', __dir__))
+ end
+ end
+
+ describe '.qa_tmp' do
+ it 'returns the fully-qualified path to the qa tmp directory' do
+ expect(described_class.qa_tmp('foo', 'bar')).to eq(File.expand_path('../../tmp/foo/bar', __dir__))
+ end
+ end
+end
diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb
index 56521cc13bc..37875ad57c9 100644
--- a/qa/spec/scenario/template_spec.rb
+++ b/qa/spec/scenario/template_spec.rb
@@ -11,7 +11,6 @@ RSpec.describe QA::Scenario::Template do
before do
stub_const('QA::Runtime::Release', release)
- stub_const('QA::Runtime::Feature', feature)
stub_const('QA::Runtime::Scenario', scenario)
stub_const('QA::Specs::Runner', runner)
@@ -26,62 +25,6 @@ RSpec.describe QA::Scenario::Template do
QA::Support::GitlabAddress.instance_variable_set(:@initialized, false)
end
- it 'allows a feature to be enabled' do
- subject.perform({ gitlab_address: gitlab_address, enable_feature: 'a-feature' })
-
- expect(feature).to have_received(:enable).with('a-feature')
- expect(feature).to have_received(:disable).with('a-feature')
- end
-
- it 'allows a feature to be disabled' do
- allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(true)
-
- subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' })
-
- expect(feature).to have_received(:disable).with('another-feature')
- expect(feature).to have_received(:enable).with('another-feature')
- end
-
- it 'does not disable a feature if already disabled' do
- allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(false)
-
- subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' })
-
- expect(feature).not_to have_received(:disable).with('another-feature')
- end
-
- it 'ensures an enabled feature is disabled afterwards' do
- allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
-
- expect { subject.perform({ gitlab_address: gitlab_address, enable_feature: 'a-feature' }) }
- .to raise_error('failed test')
-
- expect(feature).to have_received(:enable).with('a-feature')
- expect(feature).to have_received(:disable).with('a-feature')
- end
-
- it 'ensures a disabled feature is enabled afterwards' do
- allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
- allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(true)
-
- expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) }
- .to raise_error('failed test')
-
- expect(feature).to have_received(:disable).with('another-feature')
- expect(feature).to have_received(:enable).with('another-feature')
- end
-
- it 'ensures a disabled feature is not enabled afterwards if it was disabled earlier' do
- allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
- allow(QA::Runtime::Feature).to receive(:enabled?).with('another-feature').and_return(false)
-
- expect { subject.perform({ gitlab_address: gitlab_address, disable_feature: 'another-feature' }) }
- .to raise_error('failed test')
-
- expect(feature).not_to have_received(:disable).with('another-feature')
- expect(feature).not_to have_received(:enable).with('another-feature')
- end
-
it 'defines gitlab address from positional argument' do
allow(scenario).to receive(:attributes).and_return({})
diff --git a/qa/spec/scenario_shared_examples.rb b/qa/spec/scenario_shared_examples.rb
index 944e309c4cb..968bbfdd7dd 100644
--- a/qa/spec/scenario_shared_examples.rb
+++ b/qa/spec/scenario_shared_examples.rb
@@ -28,12 +28,6 @@ module QA
expect(subject).to respond_to(:perform)
end
- it 'performs before hooks only once' do
- subject.perform(args)
-
- expect(release).to have_received(:perform_before_hooks).once
- end
-
it 'sets tags on runner' do
subject.perform(args)
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 84d3199a274..2fe6fd8f325 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -4,3 +4,9 @@ require_relative '../qa'
require_relative 'scenario_shared_examples'
require_relative('../../jh/qa/spec/spec_helper') if GitlabEdition.jh?
+
+RSpec.configure do |config|
+ config.expect_with :rspec do |expectations|
+ expectations.max_formatted_output_length = nil
+ end
+end
diff --git a/qa/spec/specs/allure_report_spec.rb b/qa/spec/specs/allure_report_spec.rb
index 85befb2f602..8b01d50b4f3 100644
--- a/qa/spec/specs/allure_report_spec.rb
+++ b/qa/spec/specs/allure_report_spec.rb
@@ -3,7 +3,7 @@
describe QA::Runtime::AllureReport do
include QA::Support::Helpers::StubEnv
- let(:rspec_config) { instance_double('RSpec::Core::Configuration', 'add_formatter': nil, append_after: nil) }
+ let(:rspec_config) { instance_double('RSpec::Core::Configuration', add_formatter: nil, append_after: nil) }
let(:png_path) { 'png_path' }
let(:html_path) { 'html_path' }
diff --git a/qa/spec/specs/helpers/feature_setup_spec.rb b/qa/spec/specs/helpers/feature_setup_spec.rb
new file mode 100644
index 00000000000..07203e31aca
--- /dev/null
+++ b/qa/spec/specs/helpers/feature_setup_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+describe QA::Specs::Helpers::FeatureSetup do
+ include QA::Support::Helpers::StubEnv
+
+ let(:rspec_config) { instance_double(RSpec::Core::Configuration) }
+ let(:options) { {} }
+ let(:feature_enabled) { true }
+
+ let(:feature_flags_env) { "" }
+ let(:feature_flags) { feature_flags_env.split(",").to_h { |ff| ff.split("=") } }
+
+ before do
+ stub_env('QA_FEATURE_FLAGS', feature_flags_env)
+
+ allow(RSpec).to receive(:configure).and_yield(rspec_config)
+ allow(rspec_config).to receive(:before).with(:suite).and_yield
+ allow(rspec_config).to receive(:after).with(:suite).and_yield
+
+ allow(QA::Support::GlobalOptions).to receive(:get).and_return(options)
+ allow(QA::Runtime::Feature).to receive(:disable)
+ allow(QA::Runtime::Feature).to receive(:enable)
+ allow(QA::Runtime::Feature).to receive(:set).with(feature_flags)
+ allow(QA::Runtime::Feature).to receive(:enabled?).and_return(feature_enabled)
+ allow(QA::Runtime::Logger).to receive(:logger).and_return(instance_double(ActiveSupport::Logger, error: nil))
+
+ described_class.configure!
+ end
+
+ context "without any features configured" do
+ it "doesn't perform any operations" do
+ expect(QA::Runtime::Feature).not_to have_received(:set)
+ expect(QA::Runtime::Feature).not_to have_received(:enable)
+ expect(QA::Runtime::Feature).not_to have_received(:disable)
+ end
+ end
+
+ context "with enabling a feature" do
+ let(:options) { { enable_feature: 'a-feature' } }
+
+ context "when feature is not enabled" do
+ let(:feature_enabled) { false }
+
+ it "enables and restores feature" do
+ expect(QA::Runtime::Feature).to have_received(:enable).with(options[:enable_feature])
+ expect(QA::Runtime::Feature).to have_received(:disable).with(options[:enable_feature])
+ end
+ end
+
+ context "when feature is already enabled" do
+ it "skips feature" do
+ expect(QA::Runtime::Feature).not_to have_received(:disable)
+ expect(QA::Runtime::Feature).not_to have_received(:enable)
+ end
+ end
+ end
+
+ context "with disabling a feature" do
+ let(:options) { { disable_feature: 'a-feature' } }
+
+ context "when feature is enabled" do
+ it "disables and restore feature" do
+ expect(QA::Runtime::Feature).to have_received(:disable).with(options[:disable_feature])
+ expect(QA::Runtime::Feature).to have_received(:enable).with(options[:disable_feature])
+ end
+ end
+
+ context "when feature is already disabled" do
+ let(:feature_enabled) { false }
+
+ it "skips feature" do
+ expect(QA::Runtime::Feature).not_to have_received(:disable)
+ expect(QA::Runtime::Feature).not_to have_received(:enable)
+ end
+ end
+ end
+
+ context "with feature flags" do
+ context "with valid ff string" do
+ let(:feature_flags_env) { "some_flag=enabled,some_other_flag=disabled" }
+
+ it "sets feature flags" do
+ expect(QA::Runtime::Feature).to have_received(:set).with(feature_flags)
+ end
+ end
+
+ context "with not valid ff string" do
+ let(:feature_flags_env) { "some_flag=enabled,some_other_flag=invalid_state" }
+ let(:feature_flags) { { "some_flag" => "enabled" } }
+
+ it "skips invalid pair" do
+ expect(QA::Runtime::Feature).to have_received(:set).with(feature_flags)
+ end
+ end
+ end
+end
diff --git a/qa/spec/support/knapsack_report_spec.rb b/qa/spec/support/knapsack_report_spec.rb
index 914a30513e5..bbe560fad66 100644
--- a/qa/spec/support/knapsack_report_spec.rb
+++ b/qa/spec/support/knapsack_report_spec.rb
@@ -24,16 +24,9 @@ RSpec.describe QA::Support::KnapsackReport do
end
it 'creates a filtered file based on qa_tests' do
- expect(File).to receive(:write)
- .with('knapsack/instance-selective-parallel.json', expected_output.to_json)
+ expect(File).to receive(:write).with('knapsack/instance-selective-parallel.json', expected_output.to_json)
knapsack_report.create_for_selective(qa_tests)
end
end
-
- describe '#selective_path' do
- it 'returns the path with file name suffixed with -selective-parallel' do
- expect(knapsack_report.selective_path).to eq('knapsack/instance-selective-parallel.json')
- end
- end
end
diff --git a/qa/spec/support/ssh_spec.rb b/qa/spec/support/ssh_spec.rb
index 2edff824fd6..fe38ba7a64e 100644
--- a/qa/spec/support/ssh_spec.rb
+++ b/qa/spec/support/ssh_spec.rb
@@ -59,8 +59,8 @@ RSpec.describe QA::Support::SSH do
end
describe '#setup' do
- let(:expected_ssh_command_no_port) { "ssh-keyscan -H foo.com >> #{known_hosts_file.path}" }
- let(:expected_ssh_command_port) { "ssh-keyscan -H -p #{port} foo.com >> #{known_hosts_file.path}" }
+ let(:expected_ssh_command_no_port) { "ssh-keyscan -T 60 -H foo.com >> #{known_hosts_file.path}" }
+ let(:expected_ssh_command_port) { "ssh-keyscan -T 60 -H -p #{port} foo.com >> #{known_hosts_file.path}" }
let(:call_method) { ssh.setup }
before do
diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb
index cf5c9dea794..c8488424259 100644
--- a/qa/spec/tools/reliable_report_spec.rb
+++ b/qa/spec/tools/reliable_report_spec.rb
@@ -18,10 +18,19 @@ describe QA::Tools::ReliableReport do
let(:runs) do
values = {
- "name" => "stable spec",
+ "name" => "stable spec1",
+ "status" => "passed",
+ "file_path" => "some/spec.rb",
+ "stage" => "create",
+ "product_group" => "code_review",
+ "_time" => time
+ }
+ more_values = {
+ "name" => "stable spec2",
"status" => "passed",
"file_path" => "some/spec.rb",
"stage" => "manage",
+ "product_group" => "import_and_integrate",
"_time" => time
}
[
@@ -32,6 +41,14 @@ describe QA::Tools::ReliableReport do
instance_double("InfluxDB2::FluxRecord", values: values),
instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
]
+ ),
+ instance_double(
+ "InfluxDB2::FluxTable",
+ records: [
+ instance_double("InfluxDB2::FluxRecord", values: more_values),
+ instance_double("InfluxDB2::FluxRecord", values: more_values),
+ instance_double("InfluxDB2::FluxRecord", values: more_values.merge({ "_time" => Time.now.to_s }))
+ ]
)
]
end
@@ -42,6 +59,17 @@ describe QA::Tools::ReliableReport do
"status" => "failed",
"file_path" => "some/spec.rb",
"stage" => "create",
+ "product_group" => "code_review",
+ "failure_exception" => failure_message,
+ "job_url" => "https://job/url",
+ "_time" => time
+ }
+ more_values = {
+ "name" => "unstable spec",
+ "status" => "failed",
+ "file_path" => "some/spec.rb",
+ "stage" => "manage",
+ "product_group" => "import_and_integrate",
"failure_exception" => failure_message,
"job_url" => "https://job/url",
"_time" => time
@@ -54,6 +82,14 @@ describe QA::Tools::ReliableReport do
instance_double("InfluxDB2::FluxRecord", values: values),
instance_double("InfluxDB2::FluxRecord", values: values.merge({ "_time" => Time.now.to_s }))
]
+ ),
+ instance_double(
+ "InfluxDB2::FluxTable",
+ records: [
+ instance_double("InfluxDB2::FluxRecord", values: { **more_values, "status" => "passed" }),
+ instance_double("InfluxDB2::FluxRecord", values: more_values),
+ instance_double("InfluxDB2::FluxRecord", values: more_values.merge({ "_time" => Time.now.to_s }))
+ ]
)
]
end
@@ -82,21 +118,20 @@ describe QA::Tools::ReliableReport do
)
|> filter(fn: (r) => r["_field"] == "job_url" or
r["_field"] == "failure_exception" or
- r["_field"] == "id"
+ r["_field"] == "id" or
+ r["_field"] == "failure_issue"
)
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> group(columns: ["name"])
QUERY
end
- def markdown_section(summary, result, stage, type)
+ def expected_stage_markdown(result, stage, product_group, type)
<<~SECTION.strip
- #{summary_table(summary, type, true)}
-
- ## #{stage} (1)
+ ## #{stage.capitalize} (1)
<details>
- <summary>Executions table</summary>
+ <summary>Executions table ~\"group::#{product_group}\" (1)</summary>
#{table(result, ['NAME', 'RUNS', 'FAILURES', 'FAILURE RATE'], "Top #{type} specs in '#{stage}' stage for past #{range} days", true)}
@@ -104,7 +139,7 @@ describe QA::Tools::ReliableReport do
SECTION
end
- def summary_table(summary, type, markdown = false)
+ def expected_summary_table(summary, type, markdown = false)
table(summary, %w[STAGE COUNT], "#{type.capitalize} spec summary for past #{range} days".ljust(50), markdown)
end
@@ -117,12 +152,12 @@ describe QA::Tools::ReliableReport do
)
end
- def name_column(spec_name, exceptions_and_job_urls = {})
- "**Name**: #{spec_name}<br>**File**: [spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/specs/features/some/spec.rb)#{exceptions_markdown(exceptions_and_job_urls)}"
+ def name_column(spec_name, exceptions_and_related_urls = {})
+ "**Name**: #{spec_name}<br>**File**: [spec.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/specs/features/some/spec.rb)#{exceptions_markdown(exceptions_and_related_urls)}"
end
- def exceptions_markdown(exceptions_and_job_urls)
- exceptions_and_job_urls.empty? ? '' : "<br>**Exceptions**:<br>- [`#{failure_message}`](https://job/url)"
+ def exceptions_markdown(exceptions_and_related_urls)
+ exceptions_and_related_urls.empty? ? '' : "<br>**Exceptions**:<br>- [`#{failure_message}`](https://job/url)"
end
before do
@@ -241,28 +276,36 @@ describe QA::Tools::ReliableReport do
let(:expected_issue_body) do
<<~TXT.strip
- [[_TOC_]]
+ [[_TOC_]]
+
+ # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
- # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
+ Total amount: **2**
- Total amount: **1**
+ #{expected_summary_table([['create', 1], ['manage', 1]], :stable, true)}
- #{markdown_section([['manage', 1]], [[name_column('stable spec'), 3, 0, '0%']], 'manage', 'stable')}
+ #{expected_stage_markdown([[name_column('stable spec1'), 3, 0, '0%']], 'create', 'code review', :stable)}
- # Reliable specs with failures (#{Date.today - range} - #{Date.today})
+ #{expected_stage_markdown([[name_column('stable spec2'), 3, 0, '0%']], 'manage', 'import and integrate', :stable)}
- Total amount: **1**
+ # Reliable specs with failures (#{Date.today - range} - #{Date.today})
- #{markdown_section([['create', 1]], [[name_column('unstable spec', { failure_message => 'https://job/url' }), 3, 2, '66.67%']], 'create', 'unstable')}
+ Total amount: **2**
+
+ #{expected_summary_table([['create', 1], ['manage', 1]], :unstable, true)}
+
+ #{expected_stage_markdown([[name_column('unstable spec', { failure_message => 'https://job/url' }), 3, 2, '66.67%']], 'create', 'code review', :unstable)}
+
+ #{expected_stage_markdown([[name_column('unstable spec', { failure_message => 'https://job/url' }), 3, 2, '66.67%']], 'manage', 'import and integrate', :unstable)}
TXT
end
let(:expected_slack_text) do
<<~TEXT
- ```#{summary_table([['manage', 1]], 'stable')}```
- ```#{summary_table([['create', 1]], 'unstable')}```
+ ```#{expected_summary_table([['create', 1], ['manage', 1]], :stable)}```
+ ```#{expected_summary_table([['create', 1], ['manage', 1]], :unstable)}```
- #{issue_url}
+ #{issue_url}
TEXT
end
@@ -274,22 +317,26 @@ describe QA::Tools::ReliableReport do
let(:expected_issue_body) do
<<~TXT.strip
- [[_TOC_]]
+ [[_TOC_]]
- # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
+ # Candidates for promotion to reliable (#{Date.today - range} - #{Date.today})
- Total amount: **1**
+ Total amount: **2**
- #{markdown_section([['manage', 1]], [[name_column('stable spec'), 3, 0, '0%']], 'manage', 'stable')}
+ #{expected_summary_table([['create', 1], ['manage', 1]], :stable, true)}
+
+ #{expected_stage_markdown([[name_column('stable spec1'), 3, 0, '0%']], 'create', 'code review', :stable)}
+
+ #{expected_stage_markdown([[name_column('stable spec2'), 3, 0, '0%']], 'manage', 'import and integrate', :stable)}
TXT
end
let(:expected_slack_text) do
<<~TEXT
- ```#{summary_table([['manage', 1]], 'stable')}```
- ```#{summary_table([], 'unstable')}```
+ ```#{expected_summary_table([['create', 1], ['manage', 1]], :stable)}```
+ ```#{expected_summary_table([], :unstable)}```
- #{issue_url}
+ #{issue_url}
TEXT
end
@@ -327,4 +374,59 @@ describe QA::Tools::ReliableReport do
%q([Unable to find css "[data-testid=\"user_action_dropdown\"]"]))).to be false
end
end
+
+ describe "#exceptions_and_related_urls" do
+ subject(:reliable_report) { described_class.new(14) }
+
+ let(:failure_message) { "This is a failure exception" }
+ let(:job_url) { "https://example.com/job/url" }
+ let(:failure_issue_url) { "https://example.com/failure/issue" }
+
+ let(:records) do
+ [instance_double("InfluxDB2::FluxRecord", values: values)]
+ end
+
+ context "without failure_exception" do
+ let(:values) do
+ {
+ "failure_exception" => nil,
+ "job_url" => job_url,
+ "failure_issue" => failure_issue_url
+ }
+ end
+
+ it "returns an empty hash" do
+ expect(reliable_report.send(:exceptions_and_related_urls, records)).to be_empty
+ end
+
+ context "with failure_exception" do
+ context "without failure_issue" do
+ let(:values) do
+ {
+ "failure_exception" => failure_message,
+ "job_url" => job_url
+ }
+ end
+
+ it "returns job_url as value" do
+ expect(reliable_report.send(:exceptions_and_related_urls, records).values).to eq([job_url])
+ end
+ end
+
+ context "with failure_issue and job_url" do
+ let(:values) do
+ {
+ "failure_exception" => failure_message,
+ "failure_issue" => failure_issue_url,
+ "job_url" => job_url
+ }
+ end
+
+ it "returns failure_issue as value" do
+ expect(reliable_report.send(:exceptions_and_related_urls, records).values).to eq([failure_issue_url])
+ end
+ end
+ end
+ end
+ end
end
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index b8a8d6e1145..e1282bca4db 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -38,20 +38,24 @@ namespace :knapsack do
reports.each do |report_name|
QA::Support::KnapsackReport.new(report_name).download_report
rescue StandardError => e
- QA::Runtime::Logger.error(e)
+ QA::Runtime::Logger.error("Failed to download knapsack report '#{report_name}', error: #{e}")
end
end
desc "Create knapsack reports from existing reports for selective jobs"
task :create_reports_for_selective do
- reports = Dir.glob("knapsack/*").map { |file| file.match(%r{.*/(.*)?\.json})[1] }
+ qa_tests = ENV["QA_TESTS"]
+ if qa_tests.blank?
+ next QA::Runtime::Logger.info("QA_TESTS not set, skipping report creation for selective execution")
+ end
+ reports = Dir.glob("knapsack/*").map { |file| file.match(%r{.*/(.*)?\.json})[1] }
reports.each do |report_name|
- unless report_name.include?('-selective-parallel')
- QA::Support::KnapsackReport.new(report_name).create_for_selective(ENV['QA_TESTS'])
- end
+ next unless report_name.include?('-selective-parallel')
+
+ QA::Support::KnapsackReport.new(report_name).create_for_selective(qa_tests)
rescue StandardError => e
- QA::Runtime::Logger.error(e)
+ QA::Runtime::Logger.error("Failed to create report '#{report_name}', error: #{e}")
end
end