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/qa
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa')
-rw-r--r--qa/qa/fixtures/package_managers/composer/composer.json.erb13
-rw-r--r--qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb13
-rw-r--r--qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb12
-rw-r--r--qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb18
-rw-r--r--qa/qa/fixtures/package_managers/helm/Chart.yaml.erb6
-rw-r--r--qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb11
-rw-r--r--qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb14
-rw-r--r--qa/qa/fixtures/package_managers/maven/build_install.gradle.erb28
-rw-r--r--qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb27
-rw-r--r--qa/qa/fixtures/package_managers/maven/client_pom.xml.erb19
-rw-r--r--qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/package_pom.xml.erb22
-rw-r--r--qa/qa/fixtures/package_managers/maven/settings.xml.erb16
-rw-r--r--qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb16
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb21
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb31
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb14
-rw-r--r--qa/qa/fixtures/package_managers/npm/package_instance.json.erb8
-rw-r--r--qa/qa/fixtures/package_managers/npm/package_project.json.erb8
-rw-r--r--qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb15
-rw-r--r--qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb17
-rw-r--r--qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb19
-rw-r--r--qa/qa/fixtures/package_managers/pypi/setup.py.erb16
-rw-r--r--qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb39
-rw-r--r--qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb15
-rw-r--r--qa/qa/page/component/badges.rb53
-rw-r--r--qa/qa/page/component/blob_content.rb4
-rw-r--r--qa/qa/page/component/design_management.rb2
-rw-r--r--qa/qa/page/component/invite_members_modal.rb6
-rw-r--r--qa/qa/page/component/issuable/sidebar.rb2
-rw-r--r--qa/qa/page/component/members_filter.rb26
-rw-r--r--qa/qa/page/component/namespace_select.rb29
-rw-r--r--qa/qa/page/component/wiki_page_form.rb13
-rw-r--r--qa/qa/page/group/members.rb3
-rw-r--r--qa/qa/page/group/settings/general.rb25
-rw-r--r--qa/qa/page/main/login.rb1
-rw-r--r--qa/qa/page/merge_request/show.rb43
-rw-r--r--qa/qa/page/project/members.rb1
-rw-r--r--qa/qa/page/project/new.rb5
-rw-r--r--qa/qa/page/project/pipeline/index.rb8
-rw-r--r--qa/qa/page/project/pipeline/show.rb25
-rw-r--r--qa/qa/page/project/settings/advanced.rb15
-rw-r--r--qa/qa/page/project/settings/main.rb8
-rw-r--r--qa/qa/page/project/show.rb8
-rw-r--r--qa/qa/page/project/web_ide/edit.rb2
-rw-r--r--qa/qa/page/trials/new.rb1
-rw-r--r--qa/qa/resource/api_fabricator.rb17
-rw-r--r--qa/qa/resource/badge_base.rb25
-rw-r--r--qa/qa/resource/base.rb78
-rw-r--r--qa/qa/resource/clusters/agent.rb1
-rw-r--r--qa/qa/resource/clusters/agent_token.rb1
-rw-r--r--qa/qa/resource/fork.rb2
-rw-r--r--qa/qa/resource/group.rb14
-rw-r--r--qa/qa/resource/group_badge.rb8
-rw-r--r--qa/qa/resource/group_base.rb2
-rw-r--r--qa/qa/resource/group_milestone.rb2
-rw-r--r--qa/qa/resource/project.rb1
-rw-r--r--qa/qa/resource/project_badge.rb21
-rw-r--r--qa/qa/resource/project_web_hook.rb65
-rw-r--r--qa/qa/resource/protected_branch.rb18
-rw-r--r--qa/qa/resource/reusable.rb130
-rw-r--r--qa/qa/resource/reusable_collection.rb56
-rw-r--r--qa/qa/resource/reusable_group.rb51
-rw-r--r--qa/qa/resource/reusable_project.rb42
-rw-r--r--qa/qa/resource/sandbox.rb8
-rw-r--r--qa/qa/runtime/env.rb24
-rw-r--r--qa/qa/runtime/example.rb15
-rw-r--r--qa/qa/runtime/fixtures.rb8
-rw-r--r--qa/qa/runtime/logger.rb10
-rw-r--r--qa/qa/scenario/template.rb3
-rw-r--r--qa/qa/service/cluster_provider/gcloud.rb2
-rw-r--r--qa/qa/service/docker_run/smocker.rb56
-rw-r--r--qa/qa/service/praefect_manager.rb102
-rw-r--r--qa/qa/service/shellout.rb11
-rw-r--r--qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb2
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb12
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb22
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb8
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb8
-rw-r--r--qa/qa/specs/features/api/1_manage/project_access_token_spec.rb5
-rw-r--r--qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb70
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb21
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/praefect_dataloss_spec.rb57
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb4
-rw-r--r--qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb129
-rw-r--r--qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb37
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb16
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb72
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb31
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb98
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb45
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb43
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb192
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb147
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb214
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb112
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb88
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb62
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb63
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb76
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb8
-rw-r--r--qa/qa/specs/helpers/quarantine.rb4
-rw-r--r--qa/qa/specs/runner.rb35
-rw-r--r--qa/qa/support/api.rb12
-rw-r--r--qa/qa/support/formatters/allure_metadata_formatter.rb32
-rw-r--r--qa/qa/support/formatters/test_stats_formatter.rb111
-rw-r--r--qa/qa/support/page/logging.rb12
-rw-r--r--qa/qa/support/repeater.rb5
-rw-r--r--qa/qa/tools/delete_test_resources.rb87
-rw-r--r--qa/qa/tools/initialize_gitlab_auth.rb2
-rw-r--r--qa/qa/tools/reliable_report.rb2
-rw-r--r--qa/qa/tools/test_resource_data_processor.rb107
-rw-r--r--qa/qa/vendor/smocker/event_payload.rb49
-rw-r--r--qa/qa/vendor/smocker/history_response.rb52
-rw-r--r--qa/qa/vendor/smocker/smocker_api.rb140
142 files changed, 2694 insertions, 1331 deletions
diff --git a/qa/qa/fixtures/package_managers/composer/composer.json.erb b/qa/qa/fixtures/package_managers/composer/composer.json.erb
new file mode 100644
index 00000000000..a1e31e2599f
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/composer/composer.json.erb
@@ -0,0 +1,13 @@
+{
+ "name": "<%= project.path_with_namespace %>/<%= package.name %>",
+ "description": "Library XY",
+ "type": "library",
+ "license": "GPL-3.0-only",
+ "authors": [
+ {
+ "name": "John Doe",
+ "email": "john@example.com"
+ }
+ ],
+ "require": {}
+} \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb
new file mode 100644
index 00000000000..b6bcfafffee
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb
@@ -0,0 +1,13 @@
+publish:
+ image: curlimages/curl:latest
+ stage: build
+ variables:
+ URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN"
+ script:
+ - version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG")
+ - insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "")
+ - response=$(curl -s -w "%{http_code}" $insecure --data $version $URL)
+ - code=$(echo "$response" | tail -n 1)
+ - body=$(echo "$response" | head -n 1)
+ tags:
+ - "runner-for-<%= project.name %>" \ No newline at end of file
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
new file mode 100644
index 00000000000..39c04f6511b
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb
@@ -0,0 +1,12 @@
+image: conanio/gcc7
+
+test_package:
+ stage: deploy
+ script:
+ - conan remote add gitlab <%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/conan
+ - conan new <%= package.name %>/0.1 -t
+ - conan create . mycompany/stable
+ - "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
diff --git a/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb
new file mode 100644
index 00000000000..13fe3e2c62e
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb
@@ -0,0 +1,18 @@
+image: curlimages/curl:latest
+
+stages:
+ - upload
+ - download
+
+upload:
+ stage: upload
+ script:
+ - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file file.txt ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/<%= package.name %>/0.0.1/file.txt'
+ tags:
+ - runner-for-<%= project.name %>
+download:
+ stage: download
+ script:
+ - 'wget --header="JOB-TOKEN: $CI_JOB_TOKEN" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/<%= package.name %>/0.0.1/file.txt -O file_downloaded.txt'
+ tags:
+ - runner-for-<%= project.name %> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb b/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb
new file mode 100644
index 00000000000..5a56533c65d
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb
@@ -0,0 +1,6 @@
+apiVersion: v2
+name: <%= package_name %>
+description: GitLab QA helm package
+type: application
+version: <%= package_version %>
+appVersion: "1.16.0" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb
new file mode 100644
index 00000000000..786b0592153
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb
@@ -0,0 +1,11 @@
+pull:
+ image: alpine:3
+ script:
+ - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
+ - helm repo add --username <%= username %> --password <%= access_token %> gitlab_qa ${CI_API_V4_URL}/projects/<%= package_project.id %>/packages/helm/stable
+ - helm repo update
+ - helm pull gitlab_qa/<%= package_name %>
+ only:
+ - <%= client_project.default_branch %>
+ tags:
+ - runner-for-<%=client_project.group.name %> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb
new file mode 100644
index 00000000000..b3e907b50f4
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb
@@ -0,0 +1,14 @@
+deploy:
+ image: alpine:3
+ script:
+ - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
+ - apk add curl
+ - helm create <%= package_name %>
+ - cp ./Chart.yaml <%= package_name %>
+ - helm package <%= package_name %>
+ - http_code=$(curl --write-out "%{http_code}" --request POST --form 'chart=@<%= package_name %>-<%= package_version %>.tgz' --user <%= username %>:<%= access_token %> ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts --output /dev/null --silent)
+ - '[ $http_code = "201" ]'
+ only:
+ - <%= package_project.default_branch %>
+ tags:
+ - runner-for-<%= package_project.group.name %> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb
new file mode 100644
index 00000000000..303a64ad233
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb
@@ -0,0 +1,28 @@
+plugins {
+ id 'java'
+ id 'application'
+}
+
+repositories {
+ jcenter()
+ maven {
+ url "<%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven"
+ name "GitLab"
+ credentials(HttpHeaderCredentials) {
+ name = '<%= maven_header_name %>'
+ value = <%= token %>
+ }
+ authentication {
+ header(HttpHeaderAuthentication)
+ }
+ }
+}
+
+dependencies {
+ implementation group: '<%= group_id %>', name: '<%= artifact_id %>', version: '<%= package_version %>'
+ testImplementation 'junit:junit:4.12'
+}
+
+application {
+ mainClassName = 'gradle_maven_app.App'
+} \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb b/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb
new file mode 100644
index 00000000000..c14e63e11df
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb
@@ -0,0 +1,27 @@
+plugins {
+ id 'java'
+ id 'maven-publish'
+}
+
+publishing {
+ publications {
+ library(MavenPublication) {
+ groupId '<%= group_id %>'
+ artifactId '<%= artifact_id %>'
+ version '<%= package_version %>'
+ from components.java
+ }
+ }
+ repositories {
+ maven {
+ url "<%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven"
+ credentials(HttpHeaderCredentials) {
+ name = "Private-Token"
+ value = "<%= personal_access_token %>"
+ }
+ authentication {
+ header(HttpHeaderAuthentication)
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb b/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb
new file mode 100644
index 00000000000..20bb5f3964e
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb
@@ -0,0 +1,19 @@
+<project>
+ <groupId><%= group_id %></groupId>
+ <artifactId>maven_client</artifactId>
+ <version>1.0</version>
+ <modelVersion>4.0.0</modelVersion>
+ <repositories>
+ <repository>
+ <id><%= package_project.name %></id>
+ <url><%= gitlab_address_with_port %>/api/v4/groups/<%= package_project.group.id %>/-/packages/maven</url>
+ </repository>
+ </repositories>
+ <dependencies>
+ <dependency>
+ <groupId><%= group_id %></groupId>
+ <artifactId><%= artifact_id %></artifactId>
+ <version><%= package_version %></version>
+ </dependency>
+ </dependencies>
+</project> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb
new file mode 100644
index 00000000000..49873f124cc
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb
@@ -0,0 +1,8 @@
+ build:
+ image: gradle:6.5-jdk11
+ script:
+ - 'gradle build'
+ only:
+ - "<%= client_project.default_branch %>"
+ tags:
+ - "runner-for-<%= client_project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb
new file mode 100644
index 00000000000..3f3c7dce03c
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb
@@ -0,0 +1,8 @@
+deploy:
+ image: gradle:6.5-jdk11
+ script:
+ - 'gradle publish'
+ only:
+ - "<%= package_project.default_branch %>"
+ tags:
+ - "runner-for-<%= package_project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb
new file mode 100644
index 00000000000..78d6255e9a9
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb
@@ -0,0 +1,8 @@
+install:
+ image: maven:3.6-jdk-11
+ script:
+ - "mvn install -s settings.xml"
+ only:
+ - "<%= client_project.default_branch %>"
+ tags:
+ - "runner-for-<%= client_project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb
new file mode 100644
index 00000000000..64a63bf0bd8
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb
@@ -0,0 +1,8 @@
+ deploy:
+ image: maven:3.6-jdk-11
+ script:
+ - 'mvn deploy -s settings.xml'
+ only:
+ - "<%= package_project.default_branch %>"
+ tags:
+ - "runner-for-<%= package_project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb b/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb
new file mode 100644
index 00000000000..5159172a170
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb
@@ -0,0 +1,22 @@
+ <project>
+ <groupId><%= group_id %></groupId>
+ <artifactId><%= artifact_id %></artifactId>
+ <version><%= package_version %></version>
+ <modelVersion>4.0.0</modelVersion>
+ <repositories>
+ <repository>
+ <id><%= package_project.name %></id>
+ <url><%= gitlab_address_with_port %>/api/v4/groups/<%= package_project.group.id %>/-/packages/maven</url>
+ </repository>
+ </repositories>
+ <distributionManagement>
+ <repository>
+ <id><%= package_project.name %></id>
+ <url><%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id><%= package_project.name %></id>
+ <url><%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven</url>
+ </snapshotRepository>
+ </distributionManagement>
+</project> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/settings.xml.erb b/qa/qa/fixtures/package_managers/maven/settings.xml.erb
new file mode 100644
index 00000000000..b670b83cf85
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/settings.xml.erb
@@ -0,0 +1,16 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
+<servers>
+ <server>
+ <id><%= package_project.name %></id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name><%= maven_header_name %></name>
+ <value><%= token %></value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+</servers>
+</settings> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb b/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb
new file mode 100644
index 00000000000..611c232819f
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb
@@ -0,0 +1,16 @@
+<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
+ <servers>
+ <server>
+ <id><%= package_project.name %></id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name>Private-Token</name>
+ <value><%= personal_access_token %></value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+ </servers>
+</settings> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb
new file mode 100644
index 00000000000..a396fc98e95
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb
@@ -0,0 +1,21 @@
+image: node:latest
+
+stages:
+ - install
+
+install:
+ stage: install
+ script:
+ - "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/packages/npm/"
+ - "npm install <%= package.name %>"
+ cache:
+ key: ${CI_BUILD_REF_NAME}
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - node_modules/
+ only:
+ - "<%= another_project.default_branch %>"
+ tags:
+ - "runner-for-<%= another_project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb
new file mode 100644
index 00000000000..8d94d03ef9b
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb
@@ -0,0 +1,31 @@
+image: node:latest
+
+stages:
+ - deploy
+ - install
+
+deploy:
+ stage: deploy
+ script:
+ - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc
+ - npm publish
+ only:
+ - "<%= project.default_branch %>"
+ tags:
+ - "runner-for-<%= project.name %>"
+install:
+ stage: install
+ script:
+ - "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
+ - "npm install <%= package.name %>"
+ cache:
+ key: ${CI_BUILD_REF_NAME}
+ paths:
+ - node_modules/
+ artifacts:
+ paths:
+ - node_modules/
+ only:
+ - "<%= project.default_branch %>"
+ tags:
+ - "runner-for-<%= project.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb
new file mode 100644
index 00000000000..13c00cd17c4
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb
@@ -0,0 +1,14 @@
+image: node:latest
+
+stages:
+ - deploy
+
+deploy:
+ stage: deploy
+ script:
+ - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc
+ - npm publish
+ only:
+ - "<%= project.default_branch %>"
+ tags:
+ - "runner-for-<%= project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/npm/package_instance.json.erb b/qa/qa/fixtures/package_managers/npm/package_instance.json.erb
new file mode 100644
index 00000000000..46fecf97e2c
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/npm/package_instance.json.erb
@@ -0,0 +1,8 @@
+{
+ "name": "<%= package.name %>",
+ "version": "1.0.0",
+ "description": "Example package for GitLab npm registry",
+ "publishConfig": {
+ "@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/"
+ }
+} \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/npm/package_project.json.erb b/qa/qa/fixtures/package_managers/npm/package_project.json.erb
new file mode 100644
index 00000000000..46fecf97e2c
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/npm/package_project.json.erb
@@ -0,0 +1,8 @@
+{
+ "name": "<%= package.name %>",
+ "version": "1.0.0",
+ "description": "Example package for GitLab npm registry",
+ "publishConfig": {
+ "@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/"
+ }
+} \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb b/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb
new file mode 100644
index 00000000000..39b65a55884
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb
@@ -0,0 +1,15 @@
+image: mcr.microsoft.com/dotnet/sdk:5.0
+
+stages:
+ - install
+
+install:
+ stage: install
+ script:
+ - dotnet nuget locals all --clear
+ - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/<%= another_project.group.id %>/-/packages/nuget/index.json" --name gitlab --username <%= auth_token_username %> --password <%= auth_token_password %> --store-password-in-clear-text
+ - "dotnet add otherdotnet.csproj package <%= package.name %> --version 1.0.0"
+ only:
+ - "<%= another_project.default_branch %>"
+ tags:
+ - "runner-for-<%= project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb
new file mode 100644
index 00000000000..7c88eb49be0
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb
@@ -0,0 +1,17 @@
+image: mcr.microsoft.com/dotnet/sdk:5.0
+
+stages:
+ - deploy
+
+deploy:
+ stage: deploy
+ script:
+ - dotnet restore -p:Configuration=Release
+ - dotnet build -c Release
+ - dotnet pack -c Release -p:PackageID=<%= package.name %>
+ - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username <%= auth_token_username %> --password <%= auth_token_password %> --store-password-in-clear-text
+ - dotnet nuget push "bin/Release/*.nupkg" --source gitlab
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "<%= project.default_branch %>"'
+ tags:
+ - "runner-for-<%= project.group.name %>" \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb
new file mode 100644
index 00000000000..3ea71152801
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb
@@ -0,0 +1,19 @@
+image: python:latest
+stages:
+ - run
+ - install
+
+run:
+ stage: run
+ script:
+ - pip install twine
+ - python setup.py sdist bdist_wheel
+ - "TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*"
+ tags:
+ - runner-for-<%= project.name %>
+install:
+ stage: install
+ script:
+ - "pip install <%= package.name %> --no-deps --index-url <%= uri.scheme %>://<%= personal_access_token %>:<%= personal_access_token %>@<%= gitlab_host_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple --trusted-host <%= gitlab_host_with_port %>"
+ tags:
+ - runner-for-<%= project.name %> \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/pypi/setup.py.erb b/qa/qa/fixtures/package_managers/pypi/setup.py.erb
new file mode 100644
index 00000000000..d365f93cb5e
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/pypi/setup.py.erb
@@ -0,0 +1,16 @@
+import setuptools
+
+setuptools.setup(
+ name="<%= package.name %>",
+ version="0.0.1",
+ author="Example Author",
+ author_email="author@example.com",
+ description="A small example package",
+ packages=setuptools.find_packages(),
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+ python_requires='>=3.6',
+) \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb b/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb
new file mode 100644
index 00000000000..915deb0335d
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+Gem::Specification.new do |s|
+ s.name = '<%= package.name %>'
+ s.authors = ['Tanuki Steve', 'Hal 9000']
+ s.author = 'Tanuki Steve'
+ s.version = '0.0.1'
+ s.date = '2011-09-29'
+ s.summary = 'this is a test package'
+ s.files = ['lib/hello_gem.rb']
+ s.require_paths = ['lib']
+
+ s.description = 'A test package for GitLab.'
+ s.email = 'tanuki@not_real.com'
+ s.homepage = 'https://gitlab.com/ruby-co/my-package'
+ s.license = 'MIT'
+
+ s.metadata = {
+ 'bug_tracker_uri' => 'https://gitlab.com/ruby-co/my-package/issues',
+ 'changelog_uri' => 'https://gitlab.com/ruby-co/my-package/CHANGELOG.md',
+ 'documentation_uri' => 'https://gitlab.com/ruby-co/my-package/docs',
+ 'mailing_list_uri' => 'https://gitlab.com/ruby-co/my-package/mailme',
+ 'source_code_uri' => 'https://gitlab.com/ruby-co/my-package'
+ }
+
+ s.bindir = 'bin'
+ s.platform = Gem::Platform::RUBY
+ s.post_install_message = 'Installed, thank you!'
+ s.rdoc_options = ['--main']
+ s.required_ruby_version = '>= 2.7.0'
+ s.required_rubygems_version = '>= 1.8.11'
+ s.requirements = 'A high powered server or calculator'
+ s.rubygems_version = '1.8.09'
+
+ s.add_dependency 'dependency_1', '~> 1.2.3'
+ s.add_dependency 'dependency_2', '3.0.0'
+ s.add_dependency 'dependency_3', '>= 1.0.0'
+ s.add_dependency 'dependency_4'
+end \ No newline at end of file
diff --git a/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb
new file mode 100644
index 00000000000..29038130f1b
--- /dev/null
+++ b/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb
@@ -0,0 +1,15 @@
+image: ruby
+
+test_package:
+ stage: deploy
+ before_script:
+ - mkdir ~/.gem
+ - echo "---" > ~/.gem/credentials
+ - |
+ echo "<%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems: '${CI_JOB_TOKEN}'" >> ~/.gem/credentials
+ - chmod 0600 ~/.gem/credentials
+ script:
+ - gem build <%= package.name %>
+ - gem push <%= package.name %>-0.0.1.gem --host <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems
+ tags:
+ - runner-for-<%= project.name %> \ No newline at end of file
diff --git a/qa/qa/page/component/badges.rb b/qa/qa/page/component/badges.rb
new file mode 100644
index 00000000000..f2c5f809d8d
--- /dev/null
+++ b/qa/qa/page/component/badges.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ class Badges < Page::Base
+ 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
+ end
+
+ view 'app/assets/javascripts/badges/components/badge_list.vue' do
+ element :badge_list_content
+ element :badge_list_row
+ end
+
+ view 'app/assets/javascripts/badges/components/badge.vue' do
+ element :badge_image_link
+ end
+
+ def fill_name(name)
+ fill_element :badge_name_field, name
+ end
+
+ def fill_link_url(url)
+ fill_element :badge_link_url_field, url
+ end
+
+ def fill_image_url(url)
+ fill_element :badge_image_url_field, url
+ end
+
+ def click_add_badge_button
+ click_element :add_badge_button
+ end
+
+ def has_badge?(badge_name)
+ within_element(:badge_list_content) do
+ has_element?(:badge_list_row, badge_name: badge_name)
+ end
+ end
+
+ def has_visible_badge_image_link?(link_url)
+ within_element(:badge_list_content) do
+ has_element?(:badge_image_link, link_url: link_url)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/blob_content.rb b/qa/qa/page/component/blob_content.rb
index 4d36a6dcefe..ce743b24dda 100644
--- a/qa/qa/page/component/blob_content.rb
+++ b/qa/qa/page/component/blob_content.rb
@@ -22,6 +22,10 @@ module QA
element :copy_contents_button
end
+ base.view 'app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue' do
+ element :blob_viewer_file_content
+ end
+
base.view 'app/views/projects/blob/_header_content.html.haml' do
element :file_name_content
end
diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb
index 1f5620e30c7..73ba5713bda 100644
--- a/qa/qa/page/component/design_management.rb
+++ b/qa/qa/page/component/design_management.rb
@@ -55,7 +55,7 @@ module QA
# wait for the "Save comment" button to disappear
saved = has_no_element?(:save_comment_button)
- raise ExpectationNotMet, %q(There was a problem while adding the annotation) unless saved
+ raise RSpec::Expectations::ExpectationNotMetError, %q(There was a problem while adding the annotation) unless saved
end
def add_design(design_file_path)
diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb
index ca6862ccb02..7c536ff651b 100644
--- a/qa/qa/page/component/invite_members_modal.rb
+++ b/qa/qa/page/component/invite_members_modal.rb
@@ -9,7 +9,7 @@ module QA
def self.included(base)
super
- base.view 'app/assets/javascripts/invite_members/components/invite_members_modal.vue' do
+ base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do
element :invite_button
element :access_level_dropdown
element :invite_members_modal_content
@@ -44,9 +44,9 @@ module QA
open_invite_members_modal
within_element(:invite_members_modal_content) do
- fill_element :members_token_select_input, username
+ fill_element(:members_token_select_input, username)
Support::WaitForRequests.wait_for_requests
- click_button username
+ click_button(username, match: :prefer_exact)
set_access_level(access_level)
end
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
index 4a81230499c..921647eb4cc 100644
--- a/qa/qa/page/component/issuable/sidebar.rb
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -40,7 +40,7 @@ module QA
end
base.view 'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue' do
- element :milestone_link, 'data-qa-selector="`${issuableAttribute}_link`"' # rubocop:disable QA/ElementWithPattern
+ element :milestone_link, 'data-qa-selector="`${formatIssuableAttribute.snake}_link`"' # rubocop:disable QA/ElementWithPattern
end
base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do
diff --git a/qa/qa/page/component/members_filter.rb b/qa/qa/page/component/members_filter.rb
new file mode 100644
index 00000000000..ac07fe7e9fa
--- /dev/null
+++ b/qa/qa/page/component/members_filter.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module MembersFilter
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view 'app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue' do
+ element :members_filtered_search_bar_content
+ end
+ end
+
+ def search_member(username)
+ # TODO: Update the two actions below to use direct qa selectors once this is implemented:
+ # https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1688
+ find_element(:members_filtered_search_bar_content).find('input').set(username)
+ find('.gl-search-box-by-click-search-button').click
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/namespace_select.rb b/qa/qa/page/component/namespace_select.rb
new file mode 100644
index 00000000000..924e1af876c
--- /dev/null
+++ b/qa/qa/page/component/namespace_select.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module NamespaceSelect
+ extend QA::Page::PageConcern
+
+ def self.included(base)
+ super
+
+ base.view "app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue" do
+ element :namespaces_list
+ element :namespaces_list_groups
+ element :namespaces_list_item
+ end
+ end
+
+ def select_namespace(item)
+ click_element :namespaces_list
+
+ within_element(:namespaces_list) do
+ find_element(:namespaces_list_item, text: item).click
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/wiki_page_form.rb b/qa/qa/page/component/wiki_page_form.rb
index bc73fe0c3ab..8f504b784b2 100644
--- a/qa/qa/page/component/wiki_page_form.rb
+++ b/qa/qa/page/component/wiki_page_form.rb
@@ -38,7 +38,7 @@ module QA
def click_submit
click_element(:wiki_submit_button)
- wait_until(reload: false) do
+ QA::Support::Retrier.retry_on_exception do
has_no_element?(:wiki_title_textbox)
end
end
@@ -48,15 +48,8 @@ module QA
Page::Modal::DeleteWiki.perform(&:confirm_deletion)
end
- def use_new_editor(toggle)
- # Update once the feature is released, see https://gitlab.com/gitlab-org/gitlab/-/issues/345398
- if toggle
- click_element(:editing_mode_button, mode: 'Edit rich text')
- else
- within_element(:try_new_editor_container) do
- click_button('Use the new editor')
- end
- end
+ def use_new_editor
+ click_element(:editing_mode_button, mode: 'Edit rich text')
wait_until(reload: false) do
has_element?(:content_editor_container)
diff --git a/qa/qa/page/group/members.rb b/qa/qa/page/group/members.rb
index ccc901932f4..c80bdadb11f 100644
--- a/qa/qa/page/group/members.rb
+++ b/qa/qa/page/group/members.rb
@@ -6,6 +6,7 @@ module QA
class Members < Page::Base
include Page::Component::InviteMembersModal
include Page::Component::UsersSelect
+ include Page::Component::MembersFilter
view 'app/assets/javascripts/members/components/modals/remove_member_modal.vue' do
element :remove_member_modal_content
@@ -31,6 +32,8 @@ module QA
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
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
index 2e7ab131225..1877065f478 100644
--- a/qa/qa/page/group/settings/general.rb
+++ b/qa/qa/page/group/settings/general.rb
@@ -7,6 +7,8 @@ module QA
class General < QA::Page::Base
include ::QA::Page::Settings::Common
include Page::Component::VisibilitySetting
+ include Page::Component::ConfirmModal
+ include Page::Component::NamespaceSelect
view 'app/views/groups/edit.html.haml' do
element :permission_lfs_2fa_content
@@ -38,16 +40,6 @@ module QA
element :project_creation_level_dropdown
end
- view 'app/views/groups/settings/_transfer.html.haml' do
- element :select_group_dropdown
- element :transfer_group_button
- end
-
- view 'app/helpers/dropdowns_helper.rb' do
- element :dropdown_input_field
- element :dropdown_list_content
- end
-
def set_group_name(name)
find_element(:group_name_field).send_keys([:command, 'a'], :backspace)
find_element(:group_name_field).set name
@@ -111,17 +103,14 @@ module QA
click_element(:save_permissions_changes_button)
end
- def transfer_group(target_group)
+ def transfer_group(target_group, source_group)
expand_content :advanced_settings_content
- click_element :select_group_dropdown
- fill_element(:dropdown_input_field, target_group)
-
- within_element(:dropdown_list_content) do
- click_on target_group
- end
+ select_namespace(target_group)
+ click_element(:transfer_button)
- click_element :transfer_group_button
+ fill_confirmation_text(source_group)
+ confirm_transfer
end
end
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index f004107d7bd..a5bd37be287 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -157,6 +157,7 @@ module QA
end
def redirect_to_login_page(address)
+ Menu.perform(&:sign_out_if_signed_in)
desired_host = URI(Runtime::Scenario.send("#{address}_address")).host
Runtime::Browser.visit(address, Page::Main::Login) if desired_host != current_host
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index f8d063ac6bd..d76dfb295a0 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -83,10 +83,18 @@ module QA
element :merge_immediately_menu_item
end
+ view 'app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue' do
+ element :head_mismatch_content
+ end
+
view 'app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue' do
element :squash_checkbox
end
+ view 'app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue' do
+ element :mr_widget_content
+ end
+
view 'app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue' do
element :apply_suggestion_dropdown
element :commit_message_field
@@ -255,7 +263,8 @@ module QA
# status as unmerged, the test will fail.
# Revisit after merge page re-architect is done https://gitlab.com/groups/gitlab-org/-/epics/5598
# To remove page refresh logic if possible
- retry_until(max_attempts: 3, reload: true) do
+ # We don't raise on failure because this method is used as a predicate matcher
+ retry_until(max_attempts: 3, reload: true, raise_on_failure: false) do
has_element?(:merged_status_content, text: 'The changes were merged into', wait: 20)
end
end
@@ -269,13 +278,29 @@ module QA
has_element?(:merge_button, disabled: false)
end
- # Waits up 60 seconds and raises an error if unable to merge
- def wait_until_ready_to_merge
- has_element?(:merge_button)
+ # Waits up 60 seconds and raises an error if unable to merge.
+ #
+ # If a state is encountered in which a user would typically refresh the page, this will refresh the page and
+ # then check again if it's ready to merge. For example, it will refresh if a new change was pushed and the page
+ # needs to be refreshed to show the change.
+ #
+ # @param [Boolean] transient_test true if the current test is a transient test (default: false)
+ def wait_until_ready_to_merge(transient_test: false)
+ wait_until do
+ has_element?(:merge_button)
- # The merge button is enabled via JS
- wait_until(reload: false) do
- !find_element(:merge_button).disabled?
+ break true unless find_element(:merge_button).disabled?
+
+ # If the widget shows "Merge blocked: new changes were just added" we can refresh the page and check again
+ next false if has_element?(:head_mismatch_content)
+
+ # Stop waiting if we're in a transient test. By this point we're in an unexpected state and should let the
+ # test fail so we can investigate. If we're not in a transient test we keep trying until we reach timeout.
+ next true unless transient_test
+
+ QA::Runtime::Logger.debug("MR widget text: #{mr_widget_text}")
+
+ false
end
end
@@ -385,6 +410,10 @@ module QA
def cancel_auto_merge!
click_element(:cancel_auto_merge_button)
end
+
+ def mr_widget_text
+ find_element(:mr_widget_content).text
+ end
end
end
end
diff --git a/qa/qa/page/project/members.rb b/qa/qa/page/project/members.rb
index 1102abd6646..30748ed920b 100644
--- a/qa/qa/page/project/members.rb
+++ b/qa/qa/page/project/members.rb
@@ -5,6 +5,7 @@ module QA
module Project
class Members < Page::Base
include QA::Page::Component::InviteMembersModal
+ include QA::Page::Component::MembersFilter
view 'app/assets/javascripts/members/components/members_tabs.vue' do
element :groups_list_tab
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 42baf1f3f87..e061bc52abc 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -13,7 +13,6 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :initialize_with_readme_checkbox
- element :initialize_with_sast_checkbox
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern
@@ -21,6 +20,10 @@ module QA
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/projects/_new_project_initialize_with_sast.html.haml' do
+ element :initialize_with_sast_checkbox
+ end
+
view 'app/views/projects/project_templates/_template.html.haml' do
element :use_template_button
element :template_option_row
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index f7c5d149593..d088ba76bc0 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -26,11 +26,15 @@ module QA
end
def wait_for_latest_pipeline_succeeded
- wait_for_latest_pipeline_status { has_text?('passed') }
+ wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") }
end
def wait_for_latest_pipeline_completed
- wait_for_latest_pipeline_status { has_text?('passed') || has_text?('failed') }
+ wait_for_latest_pipeline_status { has_selector?(".ci-status-icon-success") || has_selector?(".ci-status-icon-failed") }
+ end
+
+ def wait_for_latest_pipeline_skipped
+ wait_for_latest_pipeline_status { has_text?('skipped') }
end
def wait_for_latest_pipeline_status
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 83a49ae6361..6f4757a34e8 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -18,7 +18,7 @@ module QA
view 'app/assets/javascripts/pipelines/components/graph/job_item.vue' do
element :job_item_container
element :job_link
- element :action_button
+ element :job_action_button
end
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
@@ -38,6 +38,11 @@ module QA
element :pipeline_badges
end
+ view 'app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue' do
+ element :job_dropdown_container
+ element :jobs_dropdown_menu
+ end
+
def running?(wait: 0)
within_element(:pipeline_header) do
page.has_content?('running', wait: wait)
@@ -47,7 +52,7 @@ module QA
def has_build?(name, status: :success, wait: nil)
if status
within_element(:job_item_container, text: name) do
- has_selector?(".ci-status-icon-#{status}", { wait: wait }.compact)
+ has_selector?(".ci-status-icon-#{status}", **{ wait: wait }.compact)
end
else
has_element?(:job_item_container, text: name)
@@ -110,8 +115,22 @@ module QA
end
def click_job_action(job_name)
+ wait_for_requests
+
within_element(:job_item_container, text: job_name) do
- click_element(:action_button)
+ click_element(:job_action_button)
+ end
+ end
+
+ def click_job_dropdown(job_dropdown_name)
+ click_element(:job_dropdown_container, text: job_dropdown_name)
+ end
+
+ def has_skipped_job_in_group?
+ within_element(:jobs_dropdown_menu) do
+ all_elements(:job_item_container, minimum: 1).all? do
+ has_selector?('.ci-status-icon-skipped')
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index da1f16f4cfc..525210a08f6 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -6,18 +6,13 @@ module QA
module Settings
class Advanced < Page::Base
include Component::ConfirmModal
+ include Component::NamespaceSelect
view 'app/views/projects/edit.html.haml' do
element :project_path_field
element :change_path_button
end
- view "app/assets/javascripts/vue_shared/components/namespace_select/namespace_select.vue" do
- element :namespaces_list
- element :namespaces_list_groups
- element :namespaces_list_item
- end
-
view 'app/views/projects/settings/_archive.html.haml' do
element :archive_project_link
element :unarchive_project_link
@@ -43,14 +38,6 @@ module QA
click_element :change_path_button
end
- def select_namespace(item)
- click_element :namespaces_list
-
- within_element(:namespaces_list) do
- find_element(:namespaces_list_item, text: item).click
- end
- end
-
def transfer_project!(project_name, namespace)
QA::Runtime::Logger.info "Transferring project: #{project_name} to namespace: #{namespace}"
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index 5efcb7bf23c..52ed630ac66 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -9,11 +9,13 @@ module QA
include Component::Select2
include SubMenus::Project
include Component::Breadcrumbs
+ include Layout::Flash
view 'app/views/projects/edit.html.haml' do
element :advanced_settings_content
element :merge_request_settings_content
element :visibility_features_permissions_content
+ element :badges_settings_content
end
view 'app/views/projects/settings/_general.html.haml' do
@@ -51,6 +53,12 @@ module QA
VisibilityFeaturesPermissions.perform(&block)
end
end
+
+ def expand_badges_settings(&block)
+ expand_content(:badges_settings_content) do
+ Component::Badges.perform(&block)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 8074b6f833b..4c9df2716e2 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -39,6 +39,8 @@ module QA
element :forked_from_link
element :project_name_content
element :project_id_content
+ element :project_badges_content
+ element :badge_image_link
end
view 'app/views/projects/_files.html.haml' do
@@ -179,6 +181,12 @@ module QA
has_css?('.tree-holder')
end
end
+
+ def has_visible_badge_image_link?(link_url)
+ within_element(:project_badges_content) do
+ has_element?(:badge_image_link, link_url: link_url)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 9c0a3ab691c..403c919c6e5 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -68,7 +68,7 @@ module QA
element :delete_button
end
- view 'app/views/shared/_confirm_fork_modal.html.haml' do
+ view 'app/assets/javascripts/vue_shared/components/confirm_fork_modal.vue' do
element :fork_project_button
element :confirm_fork_modal
end
diff --git a/qa/qa/page/trials/new.rb b/qa/qa/page/trials/new.rb
index 6e9d7fce688..cd3b145a89e 100644
--- a/qa/qa/page/trials/new.rb
+++ b/qa/qa/page/trials/new.rb
@@ -12,6 +12,7 @@ module QA
select :number_of_employees
text_field :telephone_number
select :country
+ select :state, id: 'state'
button :continue
end
end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 4c77c515cfd..1958884916c 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -7,11 +7,11 @@ module QA
module Resource
module ApiFabricator
include Capybara::DSL
+ include Support::API
include Errors
- attr_reader :api_resource, :api_response
attr_writer :api_client
- attr_accessor :api_user
+ attr_accessor :api_user, :api_resource, :api_response
def api_support?
respond_to?(:api_get_path) &&
@@ -48,9 +48,6 @@ module QA
end
end
- include Support::API
- attr_writer :api_resource, :api_response
-
def api_put(body = api_put_body)
response = put(
Runtime::API::Request.new(api_client, api_put_path).url,
@@ -67,6 +64,16 @@ module QA
@api_fabrication_http_method ||= :post
end
+ # Checks if a resource already exists
+ #
+ # @return [Boolean] true if the resource returns HTTP status code 200
+ def exists?
+ request = Runtime::API::Request.new(api_client, api_get_path)
+ response = get(request.url)
+
+ response.code == HTTP_STATUS_OK
+ end
+
private
def resource_web_url(resource)
diff --git a/qa/qa/resource/badge_base.rb b/qa/qa/resource/badge_base.rb
new file mode 100644
index 00000000000..5bb7eb98d4e
--- /dev/null
+++ b/qa/qa/resource/badge_base.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class BadgeBase < Base
+ attributes :id,
+ :name,
+ :link_url,
+ :image_url
+
+ def initialize
+ @name = "qa-badge-#{SecureRandom.hex(8)}"
+ end
+
+ def fabricate!
+ Page::Component::Badges.perform do |badges|
+ badges.fill_name(name)
+ badges.fill_link_url(link_url)
+ badges.fill_image_url(image_url)
+ badges.click_add_badge_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 0112e766cf0..fc7f8445d4e 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -8,6 +8,7 @@ module QA
class Base
include ApiFabricator
extend Capybara::DSL
+ using Rainbow
NoValueError = Class.new(RuntimeError)
@@ -31,7 +32,7 @@ module QA
parents = options.fetch(:parents) { [] }
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
+ log_and_record_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) }
current_url
end
@@ -47,7 +48,7 @@ module QA
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
+ log_and_record_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! }
end
end
@@ -59,7 +60,7 @@ module QA
resource.eager_load_api_client!
do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do
- log_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
+ log_and_record_fabrication(:api, resource, parents, args) { resource.remove_via_api! }
end
end
@@ -71,36 +72,17 @@ module QA
resource_web_url = yield
resource.web_url = resource_web_url
- QA::Tools::TestResourceDataProcessor.collect(resource, resource_identifier(resource))
-
resource
end
- def resource_identifier(resource)
- if resource.respond_to?(:username) && resource.username
- "with username '#{resource.username}'"
- elsif resource.respond_to?(:full_path) && resource.full_path
- "with full_path '#{resource.full_path}'"
- elsif resource.respond_to?(:name) && resource.name
- "with name '#{resource.name}'"
- elsif resource.respond_to?(:id) && resource.id
- "with id '#{resource.id}'"
- elsif resource.respond_to?(:iid) && resource.iid
- "with iid '#{resource.iid}'"
- end
- rescue QA::Resource::Base::NoValueError
- nil
- end
-
- def log_fabrication(method, resource, parents, args)
+ def log_and_record_fabrication(fabrication_method, resource, parents, args)
start = Time.now
Support::FabricationTracker.start_fabrication
result = yield.tap do
fabrication_time = Time.now - start
-
fabrication_http_method = if resource.api_fabrication_http_method == :get
- if self.include?(Reusable)
+ if include?(Reusable)
"Retrieved for reuse"
else
"Retrieved"
@@ -109,16 +91,23 @@ module QA
"Built"
end
- Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time)
+ Support::FabricationTracker.save_fabrication(:"#{fabrication_method}_fabrication", fabrication_time)
+ Tools::TestResourceDataProcessor.collect(
+ resource: resource,
+ info: resource.identifier,
+ fabrication_method: fabrication_method,
+ fabrication_time: fabrication_time
+ )
+
Runtime::Logger.debug do
msg = ["==#{'=' * parents.size}>"]
- msg << "#{fabrication_http_method} a #{name}"
- msg << resource_identifier(resource) if resource_identifier(resource)
+ msg << "#{fabrication_http_method} a #{Rainbow(name).black.bg(:white)}"
+ msg << resource.identifier
msg << "as a dependency of #{parents.last}" if parents.any?
- msg << "via #{method}"
+ msg << "via #{fabrication_method}"
msg << "in #{fabrication_time} seconds"
- msg.join(' ')
+ msg.compact.join(' ')
end
end
Support::FabricationTracker.finish_fabrication
@@ -172,7 +161,7 @@ module QA
end
def visit!(skip_resp_code_check: false)
- Runtime::Logger.debug(%(Visiting #{self.class.name} at "#{web_url}"))
+ Runtime::Logger.debug("Visiting #{Rainbow(self.class.name).black.bg(:white)} at #{web_url}")
# Just in case an async action is not yet complete
Support::WaitForRequests.wait_for_requests(skip_resp_code_check: skip_resp_code_check)
@@ -209,6 +198,35 @@ module QA
JSON.pretty_generate(comparable)
end
+ def diff(other)
+ return if self == other
+
+ diff_values = self.comparable.to_a - other.comparable.to_a
+ diff_values.to_h
+ end
+
+ def identifier
+ if respond_to?(:username) && username
+ "with username '#{username}'"
+ elsif respond_to?(:full_path) && full_path
+ "with full_path '#{full_path}'"
+ elsif respond_to?(:name) && name
+ "with name '#{name}'"
+ elsif respond_to?(:id) && id
+ "with id '#{id}'"
+ elsif respond_to?(:iid) && iid
+ "with iid '#{iid}'"
+ end
+ rescue QA::Resource::Base::NoValueError
+ nil
+ end
+
+ def remove_via_api!
+ super
+
+ Runtime::Logger.debug(["Removed a #{self.class.name}", identifier].compact.join(' '))
+ end
+
protected
# Custom resource comparison logic using resource attributes from api_resource
diff --git a/qa/qa/resource/clusters/agent.rb b/qa/qa/resource/clusters/agent.rb
index ee5a292b9b3..b190634f357 100644
--- a/qa/qa/resource/clusters/agent.rb
+++ b/qa/qa/resource/clusters/agent.rb
@@ -17,7 +17,6 @@ module QA
end
def fabricate!
- puts 'TODO: FABRICATE VIA UI'
end
def resource_web_url(resource)
diff --git a/qa/qa/resource/clusters/agent_token.rb b/qa/qa/resource/clusters/agent_token.rb
index 6d803b94564..c1cf5c2f37b 100644
--- a/qa/qa/resource/clusters/agent_token.rb
+++ b/qa/qa/resource/clusters/agent_token.rb
@@ -11,7 +11,6 @@ module QA
end
def fabricate!
- puts 'TODO: FABRICATE VIA UI'
end
def resource_web_url(resource)
diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb
index d0313670e8b..d60b90b534f 100644
--- a/qa/qa/resource/fork.rb
+++ b/qa/qa/resource/fork.rb
@@ -31,6 +31,8 @@ module QA
end
end
+ delegate :path_with_namespace, to: :project
+
def fabricate!
populate(:upstream, :user)
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index a325d96ccc2..dee63f9699c 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -3,12 +3,16 @@
module QA
module Resource
class Group < GroupBase
- attributes :require_two_factor_authentication, :description
+ attributes :require_two_factor_authentication, :description, :path
attribute :full_path do
determine_full_path
end
+ attribute :name do
+ @name || path
+ end
+
attribute :sandbox do
Sandbox.fabricate_via_api! do |sandbox|
sandbox.api_client = api_client
@@ -50,12 +54,6 @@ module QA
resource_web_url(api_get)
rescue ResourceNotFoundError
super
-
- Support::Retrier.retry_on_exception(sleep_interval: 5) do
- resource = resource_web_url(api_get)
- populate(:runners_token)
- resource
- end
end
def api_get_path
@@ -66,7 +64,7 @@ module QA
{
parent_id: sandbox.id,
path: path,
- name: path,
+ name: name || path,
visibility: 'public',
require_two_factor_authentication: @require_two_factor_authentication,
avatar: avatar
diff --git a/qa/qa/resource/group_badge.rb b/qa/qa/resource/group_badge.rb
index 3719b502b93..0c176bd5fbc 100644
--- a/qa/qa/resource/group_badge.rb
+++ b/qa/qa/resource/group_badge.rb
@@ -2,12 +2,8 @@
module QA
module Resource
- class GroupBadge < Base
- attributes :id,
- :name,
- :link_url,
- :image_url,
- :group
+ class GroupBadge < BadgeBase
+ attribute :group
# API get path
#
diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb
index 9f492a046db..05b41a4b4f6 100644
--- a/qa/qa/resource/group_base.rb
+++ b/qa/qa/resource/group_base.rb
@@ -7,6 +7,8 @@ module QA
class GroupBase < Base
include Members
+ MAX_NAME_LENGTH = 255
+
attr_accessor :path, :avatar
attributes :id,
diff --git a/qa/qa/resource/group_milestone.rb b/qa/qa/resource/group_milestone.rb
index b9ec53e929c..c208270658e 100644
--- a/qa/qa/resource/group_milestone.rb
+++ b/qa/qa/resource/group_milestone.rb
@@ -14,7 +14,7 @@ module QA
attribute :group do
Group.fabricate_via_api! do |resource|
- resource.name = 'group-with-milestone'
+ resource.name = "group-with-milestone-#{SecureRandom.hex(4)}"
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 0750ea49224..c5b72eebe03 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -17,6 +17,7 @@ module QA
attributes :id,
:name,
+ :path,
:add_name_uuid,
:description,
:runners_token,
diff --git a/qa/qa/resource/project_badge.rb b/qa/qa/resource/project_badge.rb
new file mode 100644
index 00000000000..e036999117e
--- /dev/null
+++ b/qa/qa/resource/project_badge.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class ProjectBadge < BadgeBase
+ def initialize
+ super
+
+ @link_url = "#{Runtime::Scenario.gitlab_address}/%{project_path}"
+ @image_url = "#{Runtime::Scenario.gitlab_address}/%{project_path}/badges/%{default_branch}/pipeline.svg"
+ end
+
+ def fabricate!
+ Page::Project::Menu.perform(&:go_to_general_settings)
+ Page::Project::Settings::Main.perform(&:expand_badges_settings)
+
+ super
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/project_web_hook.rb b/qa/qa/resource/project_web_hook.rb
new file mode 100644
index 00000000000..8b806c42030
--- /dev/null
+++ b/qa/qa/resource/project_web_hook.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class ProjectWebHook < Base
+ EVENT_TRIGGERS = %i[
+ issues
+ job
+ merge_requests
+ note
+ pipeline
+ push
+ tag_push
+ wiki_page
+ confidential_issues
+ confidential_note
+ ].freeze
+
+ attr_accessor :url, :enable_ssl, :id
+
+ attribute :project do
+ Project.fabricate_via_api! do |resource|
+ resource.name = 'project-with-webhooks'
+ end
+ end
+
+ EVENT_TRIGGERS.each do |trigger|
+ attribute "#{trigger}_events".to_sym do
+ false
+ end
+ end
+
+ def initialize
+ @id = nil
+ @enable_ssl = false
+ @url = nil
+ end
+
+ def resource_web_url(resource)
+ "/project/#{project.name}/~/hooks/##{resource[:id]}/edit"
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/hooks"
+ end
+
+ def api_post_path
+ api_get_path
+ end
+
+ def api_post_body
+ body = {
+ id: project.id,
+ url: url,
+ enable_ssl_verification: enable_ssl
+ }
+ EVENT_TRIGGERS.each_with_object(body) do |trigger, memo|
+ attr = "#{trigger}_events"
+ memo[attr.to_sym] = send(attr)
+ memo
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb
index 7db6450acf8..062d4e9f3d8 100644
--- a/qa/qa/resource/protected_branch.rb
+++ b/qa/qa/resource/protected_branch.rb
@@ -61,6 +61,8 @@ module QA
end
def fabricate_via_api!
+ resource_web_url(api_get)
+ rescue ResourceNotFoundError
populate_new_branch_if_required
super
@@ -75,7 +77,11 @@ module QA
end
def api_delete_path
- "/projects/#{project.id}/protected_branches/#{branch_name}"
+ api_get_path
+ end
+
+ def api_put_path
+ api_get_path
end
def api_post_path
@@ -107,6 +113,16 @@ module QA
# this particular resource does not expose a web_url property
end
+ def set_require_code_owner_approval(require = true)
+ response = patch(Runtime::API::Request.new(api_client, api_put_path).url, { code_owner_approval_required: require })
+ return if response.code == HTTP_STATUS_OK
+
+ raise(
+ ResourceUpdateFailedError,
+ "Could not update code_owner_approval_required to #{require}. Request returned (#{response.code}): `#{response}`."
+ )
+ end
+
class Roles
NO_ONE = { description: 'No one', access_level: 0 }.freeze
DEVS_AND_MAINTAINERS = { description: 'Developers + Maintainers', access_level: 30 }.freeze
diff --git a/qa/qa/resource/reusable.rb b/qa/qa/resource/reusable.rb
index 24b0a1f6bce..6a9d0392ba2 100644
--- a/qa/qa/resource/reusable.rb
+++ b/qa/qa/resource/reusable.rb
@@ -4,8 +4,13 @@ module QA
module Resource
#
# This module includes methods that allow resource classes to be reused safely. It should be prepended to a new
- # reusable version of an existing resource class. See Resource::Project and ReusableResource::Project for an example
+ # reusable version of an existing resource class. See Resource::Project and ReusableResource::Project for an example.
+ # Reusable resource classes must also be registered with a resource collection that will manage cleanup.
#
+ # @example Register a resource class with a collection
+ # QA::Resource::ReusableCollection.register_resource_classes do |collection|
+ # QA::Resource::ReusableProject.register(collection)
+ # end
module Reusable
attr_accessor :reuse,
:reuse_as
@@ -16,7 +21,7 @@ module QA
base.extend(ClassMethods)
end
- # Gets an existing resource if it exists and the parameters of the new specification of the resource are valid.
+ # Gets an existing resource if it exists and the specified attributes of the resource are valid.
# Creates a new instance of the resource if it does not exist.
#
# @return [String] The URL of the resource.
@@ -27,33 +32,128 @@ module QA
rescue Errors::ResourceNotFoundError
super
ensure
- self.class.resources[reuse_as] = self
+ self.class.resources[reuse_as] ||= {
+ tests: Set.new,
+ resource: self
+ }
+
+ self.class.resources[reuse_as][:attributes] ||= all_attributes.each_with_object({}) do |attribute_name, attributes|
+ attributes[attribute_name] = instance_variable_get("@#{attribute_name}")
+ end
+ self.class.resources[reuse_as][:tests] << Runtime::Example.location
end
- # Including classes must confirm that the resource can be reused as defined. For example, a project can't be
- # fabricated with a unique name.
+ # Overrides remove_via_api! to log a debug message stating that removal will happen after the suite completes.
#
# @return [nil]
+ def remove_via_api!
+ QA::Runtime::Logger.debug("#{self.class.name} - deferring removal until after suite")
+ end
+
+ # Object comparison
+ #
+ # @param [QA::Resource::Base] other
+ # @return [Boolean]
+ def ==(other)
+ self.class <= other.class && comparable == other.comparable
+ end
+
+ # Confirms that reuse of the resource did not change it in a way that breaks later reuse.
+ # For example, this should fail if a reusable resource should have a specific name, but the name has been changed.
+ def validate_reuse
+ QA::Runtime::Logger.debug(["Validating a #{self.class.name} that was reused as #{reuse_as}", identifier].compact.join(' '))
+
+ fresh_resource = reference_resource
+ diff = reuse_validation_diff(fresh_resource)
+
+ if diff.present?
+ raise ResourceReuseError, <<~ERROR
+ The reused #{self.class.name} resource does not have the attributes expected.
+ The following change was found: #{diff}"
+ The resource's web_url is #{web_url}.
+ It was used in these tests: #{self.class.resources[reuse_as][:tests].to_a.join(', ')}
+ ERROR
+ end
+
+ ensure
+ fresh_resource.remove_via_api!
+ end
+
+ private
+
+ # Creates a new resource that can be compared to a reused resource, using the post body of the original.
+ # Must be implemented by classes that include this module.
+ def reference_resource
+ return super if defined?(super)
+
+ raise NotImplementedError
+ end
+
+ # Confirms that the resource attributes specified in its fabricate_via_api! block will allow it to be reused.
+ #
+ # @return [nil] returns nil unless an error is raised
def validate_reuse_preconditions
+ return unless self.class.resources.key?(reuse_as)
+
+ attributes = unique_identifiers.each_with_object({ proposed: {}, existing: {} }) do |id, attrs|
+ proposed = public_send(id)
+ existing = self.class.resources[reuse_as][:resource].public_send(id)
+
+ next if proposed == existing
+
+ attrs[:proposed][id] = proposed
+ attrs[:existing][id] = existing
+ end
+
+ unless attributes[:proposed].empty? && attributes[:existing].empty?
+ raise ResourceReuseError, "Reusable resources must use the same unique identifier(s). " \
+ "The #{self.class.name} to be reused as :#{reuse_as} has the identifier(s) #{attributes[:proposed]} " \
+ "but it should have #{attributes[:existing]}"
+ end
+ end
+
+ # Compares the attributes of the current reused resource with a reference instance.
+ #
+ # @return [Hash] any differences between the resources.
+ def reuse_validation_diff(other)
+ original, reference = prepare_reuse_validation_diff(other)
+
+ return if original == reference
+
+ diff_values = original.to_a - reference.to_a
+ diff_values.to_h
+ end
+
+ # Compares the current reusable resource to a reference instance, ignoring identifying unique attributes that
+ # had to be changed.
+ #
+ # @return [Hash, Hash] the current and reference resource attributes, respectively.
+ def prepare_reuse_validation_diff(other)
+ original = self.reload!.comparable
+ reference = other.reload!.comparable
+ unique_identifiers.each { |id| reference[id] = original[id] }
+ [original, reference]
+ end
+
+ # The attributes of the resource that should be the same whenever a test wants to reuse a resource. Must be
+ # implemented by classes that include this module.
+ #
+ # @return [Array<Symbol>] the attribute names.
+ def unique_identifiers
return super if defined?(super)
raise NotImplementedError
end
module ClassMethods
- # Removes all created resources of this type.
- #
- # @return [Hash<Symbol, QA::Resource>] the resources that were to be removed.
- def remove_all_via_api!
- resources.each do |reuse_as, resource|
- QA::Runtime::Logger.debug("#{self.name} - removing #{reuse_as}")
- resource.method(:remove_via_api!).super_method.call
- end
+ # Includes the resources created/reused by this class in the specified collection
+ def register(collection)
+ collection[self.name] = resources
end
- # The resources created by this resource class.
+ # The resources created/reused by this resource class.
#
- # @return [Hash<Symbol, QA::Resource>] the resources created by this resource class.
+ # @return [Hash<Symbol, Hash>] the resources created/reused by this resource class.
def resources
@resources ||= {}
end
diff --git a/qa/qa/resource/reusable_collection.rb b/qa/qa/resource/reusable_collection.rb
new file mode 100644
index 00000000000..1168b0091fc
--- /dev/null
+++ b/qa/qa/resource/reusable_collection.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'singleton'
+
+module QA
+ module Resource
+ #
+ # This singleton class collects all reusable resources used by tests and allows operations to be performed on them
+ # all. For example, verifying their state after tests have run and might have changed them.
+ #
+ class ReusableCollection
+ include Singleton
+
+ attr_accessor :resource_classes
+
+ def initialize
+ @resource_classes = {}
+ end
+
+ # Yields each resource in the collection.
+ #
+ # @yieldparam [Symbol] reuse_as the name that identifies the resource instance.
+ # @yieldparam [QA::Resource] reuse_instance the resource.
+ def each_resource
+ resource_classes.each_value do |reuse_instances|
+ reuse_instances.each do |reuse_as, reuse_instance|
+ yield reuse_as, reuse_instance[:resource]
+ end
+ end
+ end
+
+ class << self
+ # Removes all created resources that are included in the collection.
+ def remove_all_via_api!
+ instance.each_resource do |reuse_as, resource|
+ next QA::Runtime::Logger.debug("#{resource.class.name} reused as :#{reuse_as} has already been removed.") unless resource.exists?
+
+ resource.method(:remove_via_api!).super_method.call
+ end
+ end
+
+ # Validates the reuse of each resource as defined by the resource class of each resource in the collection.
+ def validate_resource_reuse
+ instance.each_resource { |_, resource| resource.validate_reuse }
+ end
+
+ # Yields the collection of resources to allow resource classes to register themselves with the collection.
+ #
+ # @yieldparam [Hash] resource_classes the resource classes in the collection.
+ def register_resource_classes
+ yield instance.resource_classes
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/reusable_group.rb b/qa/qa/resource/reusable_group.rb
index a4bd799e85c..b75cb0517bf 100644
--- a/qa/qa/resource/reusable_group.rb
+++ b/qa/qa/resource/reusable_group.rb
@@ -8,46 +8,35 @@ module QA
def initialize
super
- @path = "reusable_group"
+ @name = @path = 'reusable_group'
@description = "QA reusable group"
@reuse_as = :default_group
end
- # Confirms that the group can be reused
- #
- # @return [nil] returns nil unless an error is raised
- def validate_reuse_preconditions
- unless reused_path_unique?
- raise ResourceReuseError,
- "Reusable groups must have the same name. The group reused as #{reuse_as} has the path '#{path}' but it should be '#{self.class.resources[reuse_as].path}'"
- end
- end
+ private
- # Confirms that reuse of the resource did not change it in a way that breaks later reuse. This raises an error if
- # the current group path doesn't match the original path.
- def validate_reuse
- reload!
-
- if api_resource[:path] != @path
- raise ResourceReuseError, "The group now has the path '#{api_resource[:path]}' but it should be '#{path}'"
- end
- end
-
- # Checks if the group is being reused with the same path.
+ # Creates a new group that can be compared to a reused group, using the attributes of the original. Attributes that
+ # must be unique (path and name) are replaced with new unique values.
#
- # @return [Boolean] true if the group's path is different from another group with the same reuse symbol (reuse_as)
- def reused_path_unique?
- return true unless self.class.resources.key?(reuse_as)
-
- self.class.resources[reuse_as].path == path
+ # @return [QA::Resource] a new instance of Resource::ReusableGroup that should be a copy of the original resource
+ def reference_resource
+ attributes = self.class.resources[reuse_as][:attributes]
+ name = "ref#{SecureRandom.hex(8)}_#{attributes.delete(:path)}"[0...MAX_NAME_LENGTH]
+
+ Group.fabricate_via_api! do |resource|
+ self.class.resources[reuse_as][:attributes].each do |attribute_name, attribute_value|
+ resource.instance_variable_set("@#{attribute_name}", attribute_value) if attribute_value
+ end
+ resource.path = name
+ resource.name = name
+ end
end
- # Overrides QA::Resource::Group#remove_via_api! to log a debug message stating that removal will happen after
- # the suite completes rather than now.
+ # The attributes of the resource that should be the same whenever a test wants to reuse a group.
#
- # @return [nil]
- def remove_via_api!
- QA::Runtime::Logger.debug("#{self.class.name} - deferring removal until after suite")
+ # @return [Array<Symbol>] the attribute names.
+ def unique_identifiers
+ [:name, :path]
end
end
end
diff --git a/qa/qa/resource/reusable_project.rb b/qa/qa/resource/reusable_project.rb
index d2dfff8ad56..b9fca314122 100644
--- a/qa/qa/resource/reusable_project.rb
+++ b/qa/qa/resource/reusable_project.rb
@@ -15,36 +15,36 @@ module QA
super
@add_name_uuid = false
- @name = "reusable_project"
+ @name = @path = 'reusable_project'
@reuse_as = :default_project
@initialize_with_readme = true
end
- # Confirms that the project can be reused
- #
- # @return [nil] returns nil unless an error is raised
- def validate_reuse_preconditions
- unless reused_name_unique?
- raise ResourceReuseError,
- "Reusable projects must have the same name. The project reused as #{reuse_as} has the name '#{name}' but it should be '#{self.class.resources[reuse_as].name}'"
- end
- end
+ private
- # Checks if the project is being reused with the same name.
+ # Creates a new project that can be compared to a reused project, using the attributes of the original. Attributes
+ # that must be unique (path and name) are replaced with new unique values.
#
- # @return [Boolean] true if the project's name is different from another project with the same reuse symbol (reuse_as)
- def reused_name_unique?
- return true unless self.class.resources.key?(reuse_as)
-
- self.class.resources[reuse_as].name == name
+ # @return [QA::Resource] a new instance of Resource::ReusableProject that should be a copy of the original resource
+ def reference_resource
+ attributes = self.class.resources[reuse_as][:attributes]
+ name = "reference_resource_#{SecureRandom.hex(8)}_for_#{attributes.delete(:name)}"
+
+ Project.fabricate_via_api! do |project|
+ self.class.resources[reuse_as][:attributes].each do |attribute_name, attribute_value|
+ project.instance_variable_set("@#{attribute_name}", attribute_value) if attribute_value
+ end
+ project.name = name
+ project.path = name
+ project.path_with_namespace = "#{project.group.full_path}/#{project.name}"
+ end
end
- # Overrides QA::Resource::Project#remove_via_api! to log a debug message stating that removal will happen after
- # the suite completes rather than now.
+ # The attributes of the resource that should be the same whenever a test wants to reuse a project.
#
- # @return [nil]
- def remove_via_api!
- QA::Runtime::Logger.debug("#{self.class.name} - deferring removal until after suite")
+ # @return [Array<Symbol>] the attribute names.
+ def unique_identifiers
+ [:name, :path]
end
end
end
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 555bfb1abc9..8e7527bccd4 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -51,14 +51,6 @@ module QA
resource_web_url(api_get)
rescue ResourceNotFoundError
super
-
- # If the group was just created the runners token might not be
- # available via the API immediately.
- Support::Retrier.retry_on_exception(sleep_interval: 5) do
- resource = resource_web_url(api_get)
- populate(:runners_token)
- resource
- end
end
def api_get_path
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 1679698a9c0..088822cc2ca 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -65,6 +65,10 @@ module QA
ENV['QA_LOG_PATH'] || $stdout
end
+ def colorized_logs?
+ enabled?(ENV['COLORIZED_LOGS'], default: false)
+ end
+
# set to 'false' to have the browser run visibly instead of headless
def webdriver_headless?
if ENV.key?('CHROME_HEADLESS')
@@ -291,6 +295,14 @@ module QA
ENV['JIRA_HOSTNAME']
end
+ # this is set by the integrations job
+ # which will allow bidirectional communication
+ # between the app and the specs container
+ # should the specs container spin up a server
+ def qa_hostname
+ ENV['QA_HOSTNAME']
+ end
+
def cache_namespace_name?
enabled?(ENV['CACHE_NAMESPACE_NAME'], default: true)
end
@@ -434,6 +446,18 @@ module QA
ENV.fetch('QA_TEST_RESOURCES_CREATED_FILEPATH', File.join(Path.qa_root, 'tmp', file_name))
end
+ def ee_activation_code
+ ENV['QA_EE_ACTIVATION_CODE']
+ end
+
+ def quarantine_disabled?
+ enabled?(ENV['DISABLE_QUARANTINE'], default: false)
+ end
+
+ def validate_resource_reuse?
+ enabled?(ENV['QA_VALIDATE_RESOURCE_REUSE'], default: false)
+ end
+
private
def remote_grid_credentials
diff --git a/qa/qa/runtime/example.rb b/qa/qa/runtime/example.rb
new file mode 100644
index 00000000000..cd2119762f5
--- /dev/null
+++ b/qa/qa/runtime/example.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Example
+ extend self
+
+ attr_accessor :current
+
+ def location
+ current.respond_to?(:location) ? current.location : 'unknown'
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb
index 05dee4bfce5..41d7ce5d178 100644
--- a/qa/qa/runtime/fixtures.rb
+++ b/qa/qa/runtime/fixtures.rb
@@ -33,6 +33,14 @@ module QA
FileUtils.remove_entry(dir, true)
end
+ def read_fixture(fixture_path, file_name)
+ file_path = Pathname
+ .new(__dir__)
+ .join("../fixtures/#{fixture_path}/#{file_name}")
+
+ File.read(file_path)
+ end
+
private
def api_client
diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb
index a70c8faf7d2..81c41000033 100644
--- a/qa/qa/runtime/logger.rb
+++ b/qa/qa/runtime/logger.rb
@@ -2,11 +2,13 @@
require 'logger'
require 'forwardable'
+require 'rainbow/refinement'
module QA
module Runtime
module Logger
extend SingleForwardable
+ using Rainbow
def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown
@@ -14,8 +16,16 @@ module QA
attr_writer :logger
def logger
+ Rainbow.enabled = Runtime::Env.colorized_logs?
+
@logger ||= ::Logger.new(Runtime::Env.log_destination).tap do |logger|
logger.level = Runtime::Env.debug? ? ::Logger::DEBUG : ::Logger::ERROR
+
+ logger.formatter = proc do |severity, datetime, progname, msg|
+ date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
+
+ "[date=#{date_format} from=QA Tests] #{severity.ljust(5)} -- ".yellow + "#{msg}\n"
+ end
end
end
end
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index ef634d3ccda..8cf1fa0705f 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -32,6 +32,9 @@ module QA
# Given *gitlab_address* = 'http://gitlab-abc123.test/' #=> http://about.gitlab-abc123.test/
Runtime::Scenario.define(:about_address, URI(-> { gitlab_address.host = "about.#{gitlab_address.host}"; gitlab_address }.call).to_s) # rubocop:disable Style/Semicolon
+ # Save the scenario class name
+ Runtime::Scenario.define(:klass, self.class.name)
+
##
# Setup knapsack and download latest report
#
diff --git a/qa/qa/service/cluster_provider/gcloud.rb b/qa/qa/service/cluster_provider/gcloud.rb
index c6d1f6cfe88..77677745f7a 100644
--- a/qa/qa/service/cluster_provider/gcloud.rb
+++ b/qa/qa/service/cluster_provider/gcloud.rb
@@ -49,7 +49,7 @@ module QA
if account.empty?
raise "Failed to login to gcloud. No credentials provided in environment and no credentials found locally."
else
- puts "gcloud account found. Using: #{account} for creating K8s cluster."
+ QA::Runtime::Logger.debug("gcloud account found. Using: #{account} for creating K8s cluster.")
end
end
end
diff --git a/qa/qa/service/docker_run/smocker.rb b/qa/qa/service/docker_run/smocker.rb
new file mode 100644
index 00000000000..83ab58887da
--- /dev/null
+++ b/qa/qa/service/docker_run/smocker.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module DockerRun
+ class Smocker < Base
+ def initialize
+ @image = 'thiht/smocker:0.17.1'
+ @name = 'smocker-server'
+ @public_port = '8080'
+ @admin_port = '8081'
+ super
+ @network_cache = network
+ end
+
+ def host_name
+ return '127.0.0.1' unless QA::Runtime::Env.running_in_ci? || QA::Runtime::Env.qa_hostname
+
+ "#{@name}.#{@network_cache}"
+ end
+
+ def base_url
+ "http://#{host_name}:#{@public_port}"
+ end
+
+ def admin_url
+ "http://#{host_name}:#{@admin_port}"
+ end
+
+ def wait_for_running
+ Support::Waiter.wait_until(raise_on_failure: false, reload_page: false) do
+ running?
+ end
+ end
+
+ def register!
+ command = <<~CMD.tr("\n", ' ')
+ docker run -d --rm
+ --network #{@network_cache}
+ --hostname #{host_name}
+ --name #{@name}
+ --publish #{@public_port}:8080
+ --publish #{@admin_port}:8081
+ #{@image}
+ CMD
+
+ unless QA::Runtime::Env.running_in_ci? || QA::Runtime::Env.qa_hostname
+ command.gsub!("--network #{@network_cache} ", '')
+ end
+
+ shell command
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index 7e47049d446..8ffb7c47652 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'digest'
+
module QA
module Service
class PraefectManager
@@ -50,6 +52,7 @@ module QA
def stop_primary_node
stop_node(@primary_node)
+ wait_until_node_is_removed_from_healthy_storages(@primary_node)
end
def start_primary_node
@@ -67,6 +70,7 @@ module QA
def stop_secondary_node
stop_node(@secondary_node)
+ wait_until_node_is_removed_from_healthy_storages(@secondary_node)
end
def start_secondary_node
@@ -75,6 +79,7 @@ module QA
def stop_tertiary_node
stop_node(@tertiary_node)
+ wait_until_node_is_removed_from_healthy_storages(@tertiary_node)
end
def start_tertiary_node
@@ -82,20 +87,41 @@ module QA
end
def start_node(name)
- shell "docker start #{name}"
- end
+ state = node_state(name)
+ return if state == "running"
+
+ if state == "paused"
+ shell "docker unpause #{name}"
+ end
+
+ if state == "stopped"
+ shell "docker start #{name}"
+ end
- def stop_node(name)
- shell "docker stop #{name}"
wait_until_shell_command_matches(
"docker inspect -f {{.State.Running}} #{name}",
- /false/,
+ /true/,
sleep_interval: 3,
max_duration: 180,
retry_on_exception: true
)
end
+ def stop_node(name)
+ return if node_state(name) == 'paused'
+
+ shell "docker pause #{name}"
+ end
+
+ def node_state(name)
+ state = "stopped"
+ wait_until_shell_command("docker inspect -f {{.State.Status}} #{name}") do |line|
+ QA::Runtime::Logger.debug(line)
+ break state = "running" if line.include?("running")
+ break state = "paused" if line.include?("paused")
+ end
+ end
+
def clear_replication_queue
QA::Runtime::Logger.info("Clearing the replication queue")
shell sql_to_docker_exec_cmd(
@@ -174,15 +200,25 @@ module QA
end
def start_all_nodes
- start_node(@postgres)
+ start_postgres
start_node(@primary_node)
start_node(@secondary_node)
start_node(@tertiary_node)
- start_node(@praefect)
+ start_praefect
wait_for_health_check_all_nodes
end
+ def start_postgres
+ start_node(@postgres)
+
+ Support::Waiter.repeat_until(max_attempts: 60, sleep_interval: 1) do
+ shell(sql_to_docker_exec_cmd("SELECT 1 as healthy_database"), fail_on_exception: false) do |line|
+ break true if line.include?("healthy_database")
+ end
+ end
+ end
+
def verify_storage_move(source_storage, destination_storage, repo_type: :project)
return if Specs::Helpers::ContextSelector.dot_com?
@@ -194,9 +230,8 @@ module QA
def wait_for_praefect
QA::Runtime::Logger.info("Waiting for health check on praefect")
Support::Waiter.wait_until(max_duration: 120, sleep_interval: 1, raise_on_failure: true) do
- # praefect runs a grpc server on port 2305, which will return an error 'Connection refused' until such time it is ready
- wait_until_shell_command("docker exec #{@gitaly_cluster} bash -c 'curl #{@praefect}:2305'") do |line|
- break if line.include?('curl: (1) Received HTTP/0.9 when not allowed')
+ wait_until_shell_command("docker exec #{@praefect} gitlab-ctl status praefect") do |line|
+ break true if line.include?('run: praefect: ')
QA::Runtime::Logger.debug(line.chomp)
end
@@ -250,6 +285,48 @@ module QA
end
end
+ def praefect_dataloss_information(project_id)
+ dataloss_info = []
+ cmd = "docker exec #{@praefect} praefect -config /var/opt/gitlab/praefect/config.toml dataloss --partially-unavailable=true"
+ shell(cmd) { |line| dataloss_info << line.strip }
+
+ # Expected will have a record for each repository in the storage, in the following format
+ # @hashed/bc/52/bc52dd634277c4a34a2d6210994a9a5e2ab6d33bb4a3a8963410e00ca6c15a02.git:
+ # Primary: gitaly1
+ # In-Sync Storages:
+ # gitaly1, assigned host
+ # gitaly3, assigned host
+ # Outdated Storages:
+ # gitaly2 is behind by 1 change or less, assigned host
+ #
+ # Alternatively, if all repositories are in sync, a concise message is returned
+ # Virtual storage: default
+ # All repositories are fully available on all assigned storages!
+
+ # extract the relevant project under test info if it is identified
+ start_index = dataloss_info.index { |line| line.include?("#{Digest::SHA256.hexdigest(project_id.to_s)}.git") }
+ unless start_index.nil?
+ dataloss_info = dataloss_info[start_index, 7]
+ end
+
+ dataloss_info&.each { |info| QA::Runtime::Logger.debug(info) }
+ dataloss_info
+ end
+
+ def praefect_dataloss_info_for_project(project_id)
+ dataloss_info = []
+ Support::Retrier.retry_until(max_duration: 60) do
+ dataloss_info = praefect_dataloss_information(project_id)
+ dataloss_info.include?("#{Digest::SHA256.hexdigest(project_id.to_s)}.git")
+ end
+ end
+
+ def wait_for_project_synced_across_all_storages(project_id)
+ Support::Retrier.retry_until(max_duration: 60) do
+ praefect_dataloss_information(project_id).include?('All repositories are fully available on all assigned storages!')
+ end
+ end
+
def wait_for_health_check_all_nodes
wait_for_gitaly_health_check(@primary_node)
wait_for_gitaly_health_check(@secondary_node)
@@ -259,9 +336,8 @@ module QA
def wait_for_gitaly_health_check(node)
QA::Runtime::Logger.info("Waiting for health check on #{node}")
Support::Waiter.wait_until(max_duration: 120, sleep_interval: 1, raise_on_failure: true) do
- # gitaly runs a grpc server on port 8075, which will return an error 'Connection refused' until such time it is ready
- wait_until_shell_command("docker exec #{@praefect} bash -c 'curl #{node}:8075'") do |line|
- break if line.include?('curl: (1) Received HTTP/0.9 when not allowed')
+ wait_until_shell_command("docker exec #{node} gitlab-ctl status gitaly") do |line|
+ break true if line.include?('run: gitaly: ')
QA::Runtime::Logger.debug(line.chomp)
end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 5a35d8c251e..33d1d10b515 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -5,6 +5,7 @@ require 'open3'
module QA
module Service
module Shellout
+ using Rainbow
CommandError = Class.new(StandardError)
module_function
@@ -13,23 +14,25 @@ module QA
# TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94
#
- def shell(command, stdin_data: nil)
- puts "Executing `#{command}`"
+ def shell(command, stdin_data: nil, fail_on_exception: true)
+ QA::Runtime::Logger.info("Executing `#{command}`".cyan)
Open3.popen2e(*command) do |stdin, out, wait|
stdin.puts(stdin_data) if stdin_data
stdin.close if stdin_data
+ cmd_output = ''
if block_given?
out.each do |line|
+ cmd_output += line
yield line
end
end
out.each_char { |char| print char }
- if wait.value.exited? && wait.value.exitstatus.nonzero?
- raise CommandError, "Command `#{command}` failed!"
+ if wait.value.exited? && wait.value.exitstatus.nonzero? && fail_on_exception
+ raise CommandError, "Command failed: #{command} \nCommand Output: #{cmd_output}"
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
index 4bc95395f25..a51d733d484 100644
--- a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :github, :requires_admin do
+ RSpec.describe 'Manage', :github, :requires_admin, :reliable do
describe 'Project import' do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } }
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb
index 8a2a382ac45..bb4b0472398 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb
@@ -3,12 +3,8 @@
require_relative 'gitlab_project_migration_common'
module QA
- RSpec.describe 'Manage', :requires_admin do
- describe 'Gitlab migration', quarantine: {
- only: { job: 'praefect' },
- type: :investigating,
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999'
- } do
+ RSpec.describe 'Manage' do
+ describe 'Gitlab migration' do
include_context 'with gitlab project migration'
context 'with project issues' do
@@ -40,13 +36,13 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347608'
) do
expect_import_finished
+ expect(imported_issues.count).to eq(1)
aggregate_failures do
- expect(imported_issues.count).to eq(1)
expect(imported_issue).to eq(source_issue.reload!)
expect(imported_comments.count).to eq(1)
- expect(imported_comments.first[:body]).to include(source_comment[:body])
+ expect(imported_comments.first&.fetch(:body)).to include(source_comment[:body])
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb
index 9dce9bff3c1..d656ea4dea5 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_mr_spec.rb
@@ -3,12 +3,8 @@
require_relative 'gitlab_project_migration_common'
module QA
- RSpec.describe 'Manage', :requires_admin do
- describe 'Gitlab migration', quarantine: {
- only: { job: 'praefect' },
- type: :investigating,
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999'
- } do
+ RSpec.describe 'Manage' do
+ describe 'Gitlab migration' do
include_context 'with gitlab project migration'
context 'with merge request' do
@@ -33,7 +29,8 @@ module QA
let!(:source_comment) { source_mr.add_comment('This is a test comment!') }
let(:imported_mrs) { imported_project.merge_requests }
- let(:imported_mr_comments) { imported_mr.comments }
+ let(:imported_mr_comments) { imported_mr.comments.map { |note| note.except(:id, :noteable_id) } }
+ let(:source_mr_comments) { source_mr.comments.map { |note| note.except(:id, :noteable_id) } }
let(:imported_mr) do
Resource::MergeRequest.init do |mr|
@@ -52,17 +49,12 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348478'
) do
expect_import_finished
+ expect(imported_mrs.count).to eq(1)
aggregate_failures do
- expect(imported_mrs.count).to eq(1)
- # TODO: remove custom comparison after member migration is implemented
- # https://gitlab.com/gitlab-org/gitlab/-/issues/341886
- expect(imported_mr.comparable.except(:author)).to eq(source_mr.reload!.comparable.except(:author))
+ expect(imported_mr).to eq(source_mr.reload!)
- expect(imported_mr_comments.count).to eq(1)
- expect(imported_mr_comments.first[:body]).to include(source_comment[:body])
- # Comment will have mention of original user since members are not migrated yet
- expect(imported_mr_comments.first[:body]).to include(other_user.name)
+ expect(imported_mr_comments).to eq(source_mr_comments)
end
end
end
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 a0c758c99e6..421dbe56a99 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
@@ -3,12 +3,8 @@
require_relative 'gitlab_project_migration_common'
module QA
- RSpec.describe 'Manage', :requires_admin do
- describe 'Gitlab migration', quarantine: {
- only: { job: 'praefect' },
- type: :investigating,
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999'
- } do
+ RSpec.describe 'Manage' do
+ describe 'Gitlab migration' do
include_context 'with gitlab project migration'
context 'with uninitialized project' do
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
index 827ebc1f5e2..b7f0a10c525 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_project_migration_common.rb
@@ -1,7 +1,13 @@
# frozen_string_literal: true
module QA
- RSpec.shared_context 'with gitlab project migration' do
+ # Disable on staging until bulk_import_projects toggle is on by default
+ # Otherwise tests running in parallel can disable feature in the middle of other test
+ RSpec.shared_context 'with gitlab project migration', :requires_admin, except: { subdomain: :staging }, quarantine: {
+ only: { job: 'praefect' },
+ type: :investigating,
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348999'
+ } do
let(:source_project_with_readme) { false }
let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
let(:admin_api_client) { Runtime::API::Client.as_admin }
diff --git a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb
index 13a795ca976..6480b880400 100644
--- a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb
@@ -78,11 +78,6 @@ module QA
@different_project.remove_via_api!
end
end
-
- after(:all) do
- @project_access_token.remove_via_api!
- @project_access_token.project.remove_via_api!
- end
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb
index 6a31d173440..fe6c89f4ee4 100644
--- a/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/user_access_termination_spec.rb
@@ -31,44 +31,50 @@ module QA
end
it 'is not allowed to push code via the CLI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347863' do
- expect do
- Resource::Repository::Push.fabricate! do |push|
- push.repository_http_uri = @project.repository_http_location.uri
- push.file_name = 'test.txt'
- push.file_content = "# This is a test project named #{@project.name}"
- push.commit_message = 'Add test.txt'
- push.branch_name = "new_branch_#{SecureRandom.hex(8)}"
- push.user = @user
- end
- end.to raise_error(QA::Support::Run::CommandError, /You are not allowed to push code to this project/)
+ QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 2) do
+ expect do
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = @project.repository_http_location.uri
+ push.file_name = 'test.txt'
+ push.file_content = "# This is a test project named #{@project.name}"
+ push.commit_message = 'Add test.txt'
+ push.branch_name = "new_branch_#{SecureRandom.hex(8)}"
+ push.user = @user
+ end
+ end.to raise_error(QA::Support::Run::CommandError, /You are not allowed to push code to this project/)
+ end
end
it 'is not allowed to create a file via the API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347864' do
- expect do
- Resource::File.fabricate_via_api! do |file|
- file.api_client = @user_api_client
- file.project = @project
- file.branch = "new_branch_#{SecureRandom.hex(8)}"
- file.commit_message = 'Add new file'
- file.name = 'test.txt'
- file.content = "New file"
- end
- end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden/)
+ QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 2) do
+ expect do
+ Resource::File.fabricate_via_api! do |file|
+ file.api_client = @user_api_client
+ file.project = @project
+ file.branch = "new_branch_#{SecureRandom.hex(8)}"
+ file.commit_message = 'Add new file'
+ file.name = 'test.txt'
+ file.content = "New file"
+ end
+ end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden/)
+ end
end
it 'is not allowed to commit via the API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347865' do
- expect do
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.api_client = @user_api_client
- commit.project = @project
- commit.branch = "new_branch_#{SecureRandom.hex(8)}"
- commit.start_branch = @project.default_branch
- commit.commit_message = 'Add new file'
- commit.add_files([
- { file_path: 'test.txt', content: 'new file' }
- ])
- end
- end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden - You are not allowed to push into this branch/)
+ QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 2) do
+ expect do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.api_client = @user_api_client
+ commit.project = @project
+ commit.branch = "new_branch_#{SecureRandom.hex(8)}"
+ commit.start_branch = @project.default_branch
+ commit.commit_message = 'Add new file'
+ commit.add_files([
+ { file_path: 'test.txt', content: 'new file' }
+ ])
+ end
+ end.to raise_error(Resource::ApiFabricator::ResourceFabricationFailedError, /403 Forbidden - You are not allowed to push into this branch/)
+ end
end
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
index 6a9be19efdd..55ae0d215cf 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/automatic_failover_and_recovery_spec.rb
@@ -9,37 +9,30 @@ module QA
project = nil
let(:intial_commit_message) { 'Initial commit' }
- let(:first_added_commit_message) { 'pushed to primary gitaly node' }
- let(:second_added_commit_message) { 'commit to failover node' }
+ let(:first_added_commit_message) { 'first_added_commit_message to primary gitaly node' }
+ let(:second_added_commit_message) { 'second_added_commit_message to failover node' }
before(:context) do
- # Reset the cluster in case previous tests left it in a bad state
praefect_manager.start_all_nodes
project = Resource::Project.fabricate! do |project|
project.name = "gitaly_cluster"
project.initialize_with_readme = true
end
- end
-
- after do
- praefect_manager.start_all_nodes
+ # We need to ensure that the the project is replicated to all nodes before proceeding with this test
+ praefect_manager.wait_for_replication(project.id)
end
it 'automatically fails over', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347830' do
- # Create a new project with a commit and wait for it to replicate
-
- # make sure that our project is published to the 'primary' node
+ # stop other nodes, so we can control which node the commit is sent to
praefect_manager.stop_secondary_node
praefect_manager.stop_tertiary_node
- praefect_manager.wait_for_secondary_node_health_check_failure
- praefect_manager.wait_for_tertiary_node_health_check_failure
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = first_added_commit_message
push.new_branch = false
- push.file_content = "This should exist on all nodes"
+ push.file_content = 'This file created on gitaly1 while gitaly2/gitaly3 not running'
end
praefect_manager.start_all_nodes
@@ -56,7 +49,7 @@ module QA
commit.add_files([
{
file_path: "file-#{SecureRandom.hex(8)}",
- content: 'This should exist on one node before reconciliation'
+ content: 'This is created on gitaly2/gitaly3 while gitaly1 is unavailable'
}
])
end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_dataloss_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_dataloss_spec.rb
new file mode 100644
index 00000000000..6e2a34afb3e
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_dataloss_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Praefect dataloss commands', :orchestrated, :gitaly_cluster do
+ let(:praefect_manager) { Service::PraefectManager.new }
+
+ let(:project) do
+ Resource::Project.fabricate! do |project|
+ project.name = 'gitaly_cluster-dataloss-project'
+ project.initialize_with_readme = true
+ end
+ end
+
+ before do
+ praefect_manager.start_all_nodes
+ end
+
+ it 'confirms that changes are synced across all storages', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352691' do
+ expect { praefect_manager.praefect_dataloss_information(project.id) }
+ .to(eventually_include('All repositories are fully available on all assigned storages!')
+ .within(max_duration: 60))
+ end
+
+ it 'identifies how many changes are not in sync across storages', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/352692' do
+ # Ensure our test repository is replicated and in a consistent state prior to test
+ praefect_manager.wait_for_project_synced_across_all_storages(project.id)
+
+ # testing for gitaly2 'out of sync'
+ praefect_manager.stop_secondary_node
+
+ number_of_changes = 3
+ 1.upto(number_of_changes) do |i|
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.branch = "newbranch-#{SecureRandom.hex(8)}"
+ commit.start_branch = project.default_branch
+ commit.commit_message = 'Add new file'
+ commit.add_files([
+ { file_path: "new_file-#{SecureRandom.hex(8)}.txt", content: 'new file' }
+ ])
+ end
+ end
+
+ # testing for gitaly3 'in sync' but marked unhealthy
+ praefect_manager.stop_tertiary_node
+
+ project_data_loss = praefect_manager.praefect_dataloss_information(project.id)
+ aggregate_failures "validate dataloss identified" do
+ expect(project_data_loss).to include('gitaly1, assigned host')
+ expect(project_data_loss).to include("gitaly2 is behind by #{number_of_changes} changes or less, assigned host, unhealthy")
+ expect(project_data_loss).to include('gitaly3, assigned host, unhealthy')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
index e7e23124312..d066953d12e 100644
--- a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
+++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb
@@ -4,7 +4,7 @@ require 'parallel'
module QA
RSpec.describe 'Create' do
- context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/346453', type: :flaky } do
+ context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env do
let(:praefect_manager) { Service::PraefectManager.new }
let(:project) do
Resource::Project.fabricate! do |project|
@@ -15,12 +15,10 @@ module QA
before do
praefect_manager.start_all_nodes
- praefect_manager.start_praefect
end
after do
praefect_manager.start_all_nodes
- praefect_manager.start_praefect
praefect_manager.clear_replication_queue
end
diff --git a/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb b/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb
new file mode 100644
index 00000000000..7a277d754c9
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/integrations/webhook_events_spec.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ describe 'WebHooks integration', :requires_admin, :integrations, :orchestrated do
+ before(:context) do
+ toggle_local_requests(true)
+ end
+
+ after(:context) do
+ Vendor::Smocker::SmockerApi.teardown!
+ end
+
+ let(:session) { SecureRandom.hex(5) }
+
+ it 'sends a push event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348945' do
+ setup_webhook(push: true) do |webhook, smocker|
+ Resource::Repository::ProjectPush.fabricate! do |project_push|
+ project_push.project = webhook.project
+ end
+
+ wait_until do
+ !smocker.history(session).empty?
+ end
+
+ events = smocker.history(session).map(&:as_hook_event)
+ aggregate_failures do
+ expect(events.size).to be(1), "Should have 1 event: \n#{events.map(&:raw).join("\n")}"
+ expect(events[0].project_name).to eql(webhook.project.name)
+ expect(events[0].push?).to be(true), "Not push event: \n#{events[0].raw}"
+ end
+ end
+ end
+
+ it 'sends a merge request event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349720' do
+ setup_webhook(merge_requests: true) do |webhook, smocker|
+ Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.project = webhook.project
+ end
+
+ wait_until do
+ !smocker.history(session).empty?
+ end
+
+ events = smocker.history(session).map(&:as_hook_event)
+ aggregate_failures do
+ expect(events.size).to be(1), "Should have 1 event: \n#{events.map(&:raw).join("\n")}"
+ expect(events[0].project_name).to eql(webhook.project.name)
+ expect(events[0].mr?).to be(true), "Not MR event: \n#{events[0].raw}"
+ end
+ end
+ end
+
+ it 'sends a wiki page event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349722' do
+ setup_webhook(wiki_page: true) do |webhook, smocker|
+ Resource::Wiki::ProjectPage.fabricate_via_api! do |page|
+ page.project = webhook.project
+ end
+
+ wait_until do
+ !smocker.history(session).empty?
+ end
+
+ events = smocker.history(session).map(&:as_hook_event)
+ aggregate_failures do
+ expect(events.size).to be(1), "Should have 1 event: \n#{events.map(&:raw).join("\n")}"
+ expect(events[0].project_name).to eql(webhook.project.name)
+ expect(events[0].wiki?).to be(true), "Not wiki event: \n#{events[0].raw}"
+ end
+ end
+ end
+
+ it 'sends an issues and note event', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349723' do
+ setup_webhook(issues: true, note: true) do |webhook, smocker|
+ issue = Resource::Issue.fabricate_via_api! do |issue_init|
+ issue_init.project = webhook.project
+ end
+
+ Resource::ProjectIssueNote.fabricate_via_api! do |note|
+ note.project = issue.project
+ note.issue = issue
+ end
+
+ wait_until do
+ smocker.history(session).size > 1
+ end
+
+ events = smocker.history(session).map(&:as_hook_event)
+ aggregate_failures do
+ issue_event = events.find(&:issue?)
+ note_event = events.find(&:note?)
+
+ expect(events.size).to be(2), "Should have 2 events: \n#{events.map(&:raw).join("\n")}"
+ expect(issue_event).not_to be(nil), "Not issue event: \n#{events[0].raw}"
+ expect(note_event).not_to be(nil), "Not note event: \n#{events[1].raw}"
+ end
+ end
+ end
+
+ private
+
+ def setup_webhook(**event_args)
+ Vendor::Smocker::SmockerApi.init(wait: 10) do |smocker|
+ smocker.register(session: session)
+
+ webhook = Resource::ProjectWebHook.fabricate_via_api! do |hook|
+ hook.url = smocker.url
+
+ event_args.each do |event, bool|
+ hook.send("#{event}_events=", bool)
+ end
+ end
+
+ yield(webhook, smocker)
+
+ smocker.reset
+ end
+ end
+
+ def toggle_local_requests(on)
+ Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: on)
+ end
+
+ def wait_until(timeout = 120, &block)
+ Support::Waiter.wait_until(max_duration: timeout, reload_page: false, raise_on_failure: false, &block)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb
index 83dcb163d56..6eb3060fb59 100644
--- a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb
+++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb
@@ -68,9 +68,10 @@ module QA
mr.iid = merge_request[:iid]
end
- expect(merge_request.state).to eq('opened')
- expect(merge_request.merge_status).to eq('checking')
- expect(merge_request.merge_when_pipeline_succeeds).to be true
+ aggregate_failures do
+ expect(merge_request.state).to eq('opened')
+ expect(merge_request.merge_when_pipeline_succeeds).to be true
+ end
end
it 'merges when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347842' do
diff --git a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb
index ecc59aa7cc8..bb4e0d71710 100644
--- a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Service ping default enabled' do
- context 'When using default enabled from gitlab.yml config', :requires_admin do
+ context 'When using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
before do
Flow::Login.sign_in_as_admin
@@ -10,7 +10,7 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end
- it 'has service ping toggle enabled' do
+ it 'has service ping toggle enabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335' do
Page::Admin::Settings::MetricsAndProfiling.perform do |setting|
setting.expand_usage_statistics do |page|
expect(page).not_to have_disabled_usage_data_checkbox
diff --git a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb
index 309369265c9..cab8bd367f5 100644
--- a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb
@@ -10,7 +10,7 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end
- it 'has service ping toggle is disabled' do
+ it 'has service ping toggle is disabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340' do
Page::Admin::Settings::MetricsAndProfiling.perform do |settings|
settings.expand_usage_statistics do |usage_statistics|
expect(usage_statistics).to have_disabled_usage_data_checkbox
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb
index a18e22f52f1..a1b9e232e3d 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- describe 'Manage', :requires_admin do
+ describe 'Manage', :requires_admin, :reliable do
describe 'Gitlab migration' do
let!(:admin_api_client) { Runtime::API::Client.as_admin }
let!(:user) do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
index 881bc5bc7c3..2db93ac60ea 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_group_spec.rb
@@ -31,7 +31,7 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347692' do
Page::Group::Menu.perform(&:click_group_general_settings_item)
Page::Group::Settings::General.perform do |general|
- general.transfer_group(target_group.path)
+ general.transfer_group(target_group.path, sub_group_for_transfer.path)
sub_group_for_transfer.sandbox = target_group
sub_group_for_transfer.reload!
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
index 098c0b3ba63..5487ecff028 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb
@@ -64,7 +64,9 @@ module QA
Page::Profile::Accounts::Show.perform do |show|
show.delete_account(user.password)
end
- Support::Waiter.wait_until { !user.exists? }
+
+ # TODO: Remove retry_on_exception once https://gitlab.com/gitlab-org/gitlab/-/issues/24294 is resolved
+ Support::Waiter.wait_until(retry_on_exception: true, sleep_interval: 3) { !user.exists? }
end
it 'allows recreating with same credentials', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347868' do
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 895027a588d..bfb810b5c2b 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -1,11 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :requires_admin, quarantine: {
- issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350598',
- type: :needs_update,
- only: { subdomain: :staging }
- } do
+ RSpec.describe 'Manage', :requires_admin do
describe 'Add project member' do
before do
Runtime::Feature.enable(:invite_members_group_modal)
@@ -25,7 +21,7 @@ module QA
Page::Project::Menu.perform(&:click_members)
Page::Project::Members.perform do |members|
members.add_member(user.username)
-
+ members.search_member(user.username)
expect(members).to have_content("@#{user.username}")
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
new file mode 100644
index 00000000000..2933d580957
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_badge_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'Create project badge' do
+ let(:badge_name) { "project-badge-#{SecureRandom.hex(8)}" }
+ let(:expected_badge_link_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}" }
+ let(:expected_badge_image_url) { "#{Runtime::Scenario.gitlab_address}/#{project.path_with_namespace}/badges/main/pipeline.svg" }
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'badge-test-project'
+ project.initialize_with_readme = true
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ end
+
+ it 'creates project badge successfully', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/350065' do
+ Resource::ProjectBadge.fabricate! do |badge|
+ badge.name = badge_name
+ end
+
+ Page::Project::Settings::Main.perform do |project_settings|
+ expect(project_settings).to have_notice('New badge added.')
+ end
+
+ Page::Component::Badges.perform do |badges|
+ aggregate_failures do
+ expect(badges).to have_badge(badge_name)
+ expect(badges).to have_visible_badge_image_link(expected_badge_link_url)
+ expect(badges.asset_exists?(expected_badge_image_url)).to be_truthy
+ end
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project|
+ expect(project).to have_visible_badge_image_link(expected_badge_link_url)
+ expect(project.asset_exists?(expected_badge_image_url)).to be_truthy
+ end
+ end
+
+ after do
+ project&.remove_via_api!
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
index 714c4a2da67..4f9ba579730 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/protected_tags_spec.rb
@@ -21,8 +21,8 @@ module QA
let(:tag_message) { 'Version 0.0.1' }
let(:tag_release_notes) { 'Release It!' }
- shared_examples 'successful tag creation' do |user|
- it "can be created by #{user}" do
+ shared_examples 'successful tag creation' do |user, testcase|
+ it "can be created by #{user}", testcase: testcase do
Flow::Login.sign_in(as: send(user))
create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
@@ -36,8 +36,8 @@ module QA
end
end
- shared_examples 'unsuccessful tag creation' do |user|
- it "cannot be created by an unauthorized #{user}" do
+ shared_examples 'unsuccessful tag creation' do |user, testcase|
+ it "cannot be created by an unauthorized #{user}", testcase: testcase do
Flow::Login.sign_in(as: send(user))
create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
@@ -54,8 +54,8 @@ module QA
add_members_to_project(project)
end
- it_behaves_like 'successful tag creation', :developer_user
- it_behaves_like 'successful tag creation', :maintainer_user
+ it_behaves_like 'successful tag creation', :developer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347930'
+ it_behaves_like 'successful tag creation', :maintainer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347929'
end
context 'when protected' do
@@ -69,8 +69,8 @@ module QA
Page::Main::Menu.perform(&:sign_out)
end
- it_behaves_like 'unsuccessful tag creation', :developer_user
- it_behaves_like 'successful tag creation', :maintainer_user
+ it_behaves_like 'unsuccessful tag creation', :developer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347927'
+ it_behaves_like 'successful tag creation', :maintainer_user, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347928'
end
def create_tag_for_project(project, name, message, release_notes)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
index 87b51edef08..11cf4f60a80 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/follow_user_activity_spec.rb
@@ -5,21 +5,27 @@ module QA
describe 'User', :requires_admin do
let(:admin_api_client) { Runtime::API::Client.as_admin }
- let(:user) do
+ let(:followed_user_api_client) { Runtime::API::Client.new(:gitlab, user: followed_user) }
+
+ let(:followed_user) do
Resource::User.fabricate_via_api! do |user|
+ user.name = "followed_user_#{SecureRandom.hex(8)}"
user.api_client = admin_api_client
end
end
- let(:user_api_client) do
- Runtime::API::Client.new(:gitlab, user: user)
+ let(:following_user) do
+ Resource::User.fabricate_via_api! do |user|
+ user.name = "following_user_#{SecureRandom.hex(8)}"
+ user.api_client = admin_api_client
+ end
end
let(:group) do
group = QA::Resource::Group.fabricate_via_api! do |group|
group.path = "group_for_follow_user_activity_#{SecureRandom.hex(8)}"
end
- group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
+ group.add_member(followed_user, Resource::Members::AccessLevel::MAINTAINER)
group
end
@@ -27,7 +33,7 @@ module QA
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-for-tags'
project.initialize_with_readme = true
- project.api_client = user_api_client
+ project.api_client = followed_user_api_client
project.group = group
end
end
@@ -35,14 +41,14 @@ module QA
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
- mr.api_client = user_api_client
+ mr.api_client = followed_user_api_client
end
end
let(:issue) do
Resource::Issue.fabricate_via_api! do |issue|
issue.project = project
- issue.api_client = user_api_client
+ issue.api_client = followed_user_api_client
end
end
@@ -51,19 +57,19 @@ module QA
project_issue_note.project = project
project_issue_note.issue = issue
project_issue_note.body = 'This is a comment'
- project_issue_note.api_client = user_api_client
+ project_issue_note.api_client = followed_user_api_client
end
end
before do
# Create both tokens before logging in the first time so that we don't need to log out in the middle of the test
admin_api_client.personal_access_token
- user_api_client.personal_access_token
+ followed_user_api_client.personal_access_token
end
it 'can be followed and their activity seen', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347678' do
- Flow::Login.sign_in
- page.visit Runtime::Scenario.gitlab_address + "/#{user.username}"
+ Flow::Login.sign_in(as: following_user)
+ page.visit Runtime::Scenario.gitlab_address + "/#{followed_user.username}"
Page::User::Show.perform(&:click_follow_user_link)
expect(page).to have_text("No activities found")
@@ -76,7 +82,7 @@ module QA
Page::Main::Menu.perform(&:click_user_profile_link)
Page::User::Show.perform do |show|
show.click_following_link
- show.click_user_link(user.username)
+ show.click_user_link(followed_user.username)
aggregate_failures do
expect(show).to have_activity('created project')
@@ -88,9 +94,10 @@ module QA
end
after do
- project.api_client = admin_api_client
- project.remove_via_api!
- user.remove_via_api!
+ project&.api_client = admin_api_client
+ project&.remove_via_api!
+ followed_user&.remove_via_api!
+ following_user&.remove_via_api!
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
index c908b1c46a1..d3662884952 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb
@@ -31,8 +31,8 @@ module QA
Flow::Login.sign_in
end
- shared_examples 'milestone assigned to existing issue' do
- it 'is assigned to an existing issue' do
+ shared_examples 'milestone assigned to existing issue' do |testcase|
+ it 'is assigned to an existing issue', testcase: testcase do
issue.visit!
Page::Project::Issue::Show.perform do |existing_issue|
@@ -43,8 +43,8 @@ module QA
end
end
- shared_examples 'milestone assigned to new issue' do
- it 'is assigned to a new issue' do
+ shared_examples 'milestone assigned to new issue' do |testcase|
+ it 'is assigned to a new issue', testcase: testcase do
Resource::Issue.fabricate_via_browser_ui! do |new_issue|
new_issue.project = project
new_issue.milestone = milestone
@@ -65,8 +65,8 @@ module QA
end
end
- it_behaves_like 'milestone assigned to existing issue'
- it_behaves_like 'milestone assigned to new issue'
+ it_behaves_like 'milestone assigned to existing issue', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347964'
+ it_behaves_like 'milestone assigned to new issue', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347965'
end
context 'Project milestone' do
@@ -78,8 +78,8 @@ module QA
end
end
- it_behaves_like 'milestone assigned to existing issue'
- it_behaves_like 'milestone assigned to new issue'
+ it_behaves_like 'milestone assigned to existing issue', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347962'
+ it_behaves_like 'milestone assigned to new issue', 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347963'
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
index 5f896c7bf10..b7284f972ef 100644
--- a/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/design_management/add_design_content_spec.rb
@@ -12,7 +12,7 @@ module QA
Flow::Login.sign_in
end
- it 'user adds a design and annotates it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do
+ it 'user adds a design and annotates it', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/352746', type: :investigating }, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347822' do
issue.visit!
Page::Project::Issue::Show.perform do |issue|
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
index 9a771919c11..85270791f0f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb
@@ -20,6 +20,18 @@ module QA
end
before do
+ Flow::Login.sign_in
+ end
+
+ after do
+ runner&.remove_via_api!
+ project&.remove_via_api!
+ end
+
+ it 'merges after pipeline succeeds' do
+ transient_test = repeat > 1
+
+ # Push a new pipeline config file
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
@@ -30,7 +42,7 @@ module QA
content: <<~EOF
test:
tags: ["runner-for-#{project.name}"]
- script: sleep 20
+ script: sleep 30
only:
- merge_requests
EOF
@@ -39,17 +51,8 @@ module QA
)
end
- Flow::Login.sign_in
- end
-
- after do
- runner&.remove_via_api!
- project&.remove_via_api!
- end
-
- it 'merges after pipeline succeeds' do
repeat.times do |i|
- QA::Runtime::Logger.info("Transient bug test - Trial #{i}") if repeat > 1
+ QA::Runtime::Logger.info("Transient bug test - Trial #{i}") if transient_test
branch_name = "mr-test-#{SecureRandom.hex(6)}-#{i}"
@@ -68,19 +71,54 @@ module QA
merge_request.no_preparation = true
end
+ # Load the page so that the browser is as prepared as possible to display the pipeline in progress when we
+ # start it.
merge_request.visit!
+ # Push a new file to trigger a new pipeline
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add new file'
+ commit.branch = branch_name
+ commit.add_files(
+ [
+ {
+ file_path: "#{branch_name}-file.md",
+ content: "file content"
+ }
+ ]
+ )
+ end
+
Page::MergeRequest::Show.perform do |mr|
- mr.merge_when_pipeline_succeeds!
+ mr.refresh
+
+ # Part of the challenge with this test is that the MR widget has many components that could be displayed
+ # and many errors states that those components could encounter. Most of the time few of those
+ # possible components will be relevant, so it would be inefficient for this test to check for each of
+ # them. Instead, we fail on anything but the expected state.
+ #
+ # The following method allows us to handle and ignore states (as we find them) that users could safely ignore.
+ mr.wait_until_ready_to_merge(transient_test: transient_test)
+
+ mr.retry_until(reload: true, message: 'Wait until ready to click MWPS') do
+ merge_request.reload!
+
+ # Click the MWPS button if we can
+ break mr.merge_when_pipeline_succeeds! if mr.has_element?(:merge_button, text: 'Merge when pipeline succeeds')
+
+ # But fail if the button is missing because the pipeline is complete
+ raise "The pipeline already finished before we could click MWPS" if mr.wait_until { project.pipelines.first }[:status] == 'success'
- Support::Waiter.wait_until(sleep_interval: 5) do
- merge_request = merge_request.reload!
- merge_request.state == 'merged'
+ # Otherwise, if this is not a transient test reload the page and retry
+ next false unless transient_test
end
aggregate_failures do
- expect(merge_request.merge_when_pipeline_succeeds).to be_truthy
- expect(mr.merged?).to be_truthy, "Expected content 'The changes were merged' but it did not appear."
+ expect { mr.merged? }.to eventually_be_truthy.within(max_duration: 60), "Expected content 'The changes were merged' but it did not appear."
+ expect(merge_request.reload!.merge_when_pipeline_succeeds).to be_truthy
+ expect(merge_request.state).to eq('merged')
+ expect(project.pipelines.last[:status]).to eq('success')
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index 3da73c8fa72..107d72a9724 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -18,30 +18,34 @@ module QA
file_name: '.gitignore',
name: 'Android',
api_path: 'gitignores',
- api_key: 'Android'
+ api_key: 'Android',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347659'
},
{
file_name: '.gitlab-ci.yml',
name: 'Julia',
api_path: 'gitlab_ci_ymls',
- api_key: 'Julia'
+ api_key: 'Julia',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347658'
},
{
file_name: 'Dockerfile',
name: 'Python',
api_path: 'dockerfiles',
- api_key: 'Python'
+ api_key: 'Python',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347660'
},
{
file_name: 'LICENSE',
name: 'Mozilla Public License 2.0',
api_path: 'licenses',
- api_key: 'mpl-2.0'
+ api_key: 'mpl-2.0',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347657'
}
]
templates.each do |template|
- it "user adds #{template[:file_name]} via file template #{template[:name]}" do
+ 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
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb
new file mode 100644
index 00000000000..78abdb94dfe
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create', only: { subdomain: %i[staging staging-canary] } do
+ describe 'Git push to canary Gitaly node over HTTP' do
+ it 'pushes to a project using a canary specific Gitaly repository storage', :smoke, :requires_admin, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/351116' do
+ Flow::Login.sign_in_as_admin
+
+ project = Resource::Project.fabricate_via_api! do |storage_project|
+ storage_project.name = 'canary-specific-repository-storage'
+ storage_project.repository_storage = 'nfs-file-cny01' # TODO: move to ENV var
+ end
+
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = project.repository_http_location.uri
+ push.file_name = 'README.md'
+ push.file_content = "# This is a test project named #{project.name}"
+ push.commit_message = 'Add README.md'
+ push.new_branch = true
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project_page|
+ expect(project_page).to have_file('README.md')
+ expect(project_page).to have_readme_content("This is a test project named #{project.name}")
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb
index 6ab50ba56f2..1a7c64a363f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb
@@ -28,8 +28,8 @@ module QA
project_snippet&.remove_via_api!
end
- shared_examples 'comments on snippets' do |snippet_type|
- it "adds, edits, and deletes a comment on a #{snippet_type}" do
+ shared_examples 'comments on snippets' do |snippet_type, testcase|
+ it "adds, edits, and deletes a comment on a #{snippet_type}", testcase: testcase do
send(snippet_type)
Page::Main::Menu.perform(&:sign_out)
@@ -49,8 +49,8 @@ module QA
end
end
- it_behaves_like 'comments on snippets', :personal_snippet
- it_behaves_like 'comments on snippets', :project_snippet
+ it_behaves_like 'comments on snippets', :personal_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347816'
+ it_behaves_like 'comments on snippets', :project_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347817'
def create_comment
Page::Dashboard::Snippet::Show.perform do |snippet|
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb
index 72d83eadde9..8f05446ff70 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb
@@ -28,8 +28,8 @@ module QA
project_snippet&.remove_via_api!
end
- shared_examples 'adding file to snippet' do |snippet_type|
- it "adds second file to an existing #{snippet_type} to make it multi-file" do
+ shared_examples 'adding file to snippet' do |snippet_type, testcase|
+ it "adds second file to an existing #{snippet_type} to make it multi-file", testcase: testcase do
send(snippet_type).visit!
Page::Dashboard::Snippet::Show.perform(&:click_edit_button)
@@ -52,8 +52,8 @@ module QA
end
end
- it_behaves_like 'adding file to snippet', :personal_snippet
- it_behaves_like 'adding file to snippet', :project_snippet
+ it_behaves_like 'adding file to snippet', :personal_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347845'
+ it_behaves_like 'adding file to snippet', :project_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347846'
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
index 29ddbb22a01..0b63d9a1edb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb
@@ -59,8 +59,8 @@ module QA
project_snippet&.remove_via_api!
end
- shared_examples 'copying snippet file contents' do |snippet_type|
- it "copies file contents of a multi-file #{snippet_type} to a comment and verifies them" do
+ shared_examples 'copying snippet file contents' do |snippet_type, testcase|
+ it "copies file contents of a multi-file #{snippet_type} to a comment and verifies them", testcase: testcase do
send(snippet_type).visit!
files.each do |files|
@@ -73,8 +73,8 @@ module QA
end
end
- it_behaves_like 'copying snippet file contents', :personal_snippet
- it_behaves_like 'copying snippet file contents', :project_snippet
+ it_behaves_like 'copying snippet file contents', :personal_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347849'
+ it_behaves_like 'copying snippet file contents', :project_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347848'
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
index dc66e0c5a9f..e04f580dc15 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create' do # convert back to a smoke test once proved to be stable
+ RSpec.describe 'Create', :smoke do
describe 'Personal snippet creation' do
let(:snippet) do
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
index 014c0ca4d48..b6092ef0c4c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb
@@ -36,8 +36,8 @@ module QA
project_snippet&.remove_via_api!
end
- shared_examples 'deleting file from snippet' do |snippet_type|
- it "deletes second file from an existing #{snippet_type} to make it single-file" do
+ shared_examples 'deleting file from snippet' do |snippet_type, testcase|
+ it "deletes second file from an existing #{snippet_type} to make it single-file", testcase: testcase do
send(snippet_type).visit!
Page::Dashboard::Snippet::Show.perform(&:click_edit_button)
@@ -58,8 +58,8 @@ module QA
end
end
- it_behaves_like 'deleting file from snippet', :personal_snippet
- it_behaves_like 'deleting file from snippet', :project_snippet
+ it_behaves_like 'deleting file from snippet', :personal_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347728'
+ it_behaves_like 'deleting file from snippet', :project_snippet, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347727'
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb
index d922950335f..97e42edfd01 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb
@@ -56,8 +56,8 @@ module QA
project_snippet_with_multiple_files.remove_via_api!
end
- shared_examples 'displaying details on index page' do |snippet_type|
- it "shows correct details of #{snippet_type} including file number" do
+ shared_examples 'displaying details on index page' do |snippet_type, testcase|
+ it "shows correct details of #{snippet_type} including file number", testcase: testcase do
send(snippet_type)
Page::Main::Menu.perform do |menu|
menu.go_to_menu_dropdown_option(:snippets_link)
@@ -73,10 +73,10 @@ module QA
end
end
- it_behaves_like 'displaying details on index page', :personal_snippet_with_single_file
- it_behaves_like 'displaying details on index page', :personal_snippet_with_multiple_files
- it_behaves_like 'displaying details on index page', :project_snippet_with_single_file
- it_behaves_like 'displaying details on index page', :project_snippet_with_multiple_files
+ it_behaves_like 'displaying details on index page', :personal_snippet_with_single_file, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347717'
+ it_behaves_like 'displaying details on index page', :personal_snippet_with_multiple_files, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347720'
+ it_behaves_like 'displaying details on index page', :project_snippet_with_single_file, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347718'
+ it_behaves_like 'displaying details on index page', :project_snippet_with_multiple_files, 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347719'
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index 70c9c9beeb8..19dd868744f 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -18,30 +18,34 @@ module QA
file_name: '.gitignore',
name: 'Android',
api_path: 'gitignores',
- api_key: 'Android'
+ 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'
+ 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'
+ 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'
+ 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]}" do
+ 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
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
index 758aae9f729..96e85139e78 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb
@@ -11,7 +11,7 @@ module QA
end
context 'when a user does not have permissions to commit to the project' do
- let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
+ let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
context 'when no fork is present' do
it 'suggests to create a fork when a user clicks Web IDE in the main project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347823' do
@@ -45,6 +45,10 @@ module QA
submit_merge_request_upstream
end
+
+ after do
+ fork_project.project.remove_via_api!
+ end
end
def submit_merge_request_upstream
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
index 67eee66b3d6..b45624381c8 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/content_editor_spec.rb
@@ -1,13 +1,12 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Create', :requires_admin, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350220', type: :investigating } do # remove :requires_admin once the ff is enabled by default in https://gitlab.com/gitlab-org/gitlab/-/issues/345398
+ RSpec.describe 'Create' do
context 'Content Editor' do
let(:initial_wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! }
let(:page_title) { 'Content Editor Page' }
let(:heading_text) { 'My New Heading' }
let(:image_file_name) { 'testfile.png' }
- let!(:toggle) { Runtime::Feature.enabled?(:wiki_switch_between_content_editor_raw_markdown) }
before do
Flow::Login.sign_in
@@ -24,7 +23,7 @@ module QA
Page::Project::Wiki::Edit.perform do |edit|
edit.set_title(page_title)
- edit.use_new_editor(toggle)
+ edit.use_new_editor
edit.add_heading('Heading 1', heading_text)
edit.upload_image(File.absolute_path(File.join('qa', 'fixtures', 'designs', image_file_name)))
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
index 22bb5fed84c..0bc3fb7b829 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb
@@ -25,7 +25,7 @@ module QA
Resource::Runner.fabricate! do |runner|
runner.name = executor
runner.tags = [executor]
- runner.token = group.sandbox.runners_token
+ runner.token = group.reload!.runners_token
end
end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb
new file mode 100644
index 00000000000..7a2c2b4ae90
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Verify', :runner, quarantine: {
+ type: :flaky,
+ issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/351994'
+ } do
+ describe 'Run pipeline with manual jobs' do
+ let(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'pipeline-with-manual-job'
+ project.description = 'Project for pipeline with manual job'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = "qa-runner-#{SecureRandom.hex(3)}"
+ end
+ end
+
+ let!(:ci_file) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files(
+ [
+ {
+ file_path: '.gitlab-ci.yml',
+ content: <<~YAML
+ stages:
+ - Stage1
+ - Stage2
+ - Stage3
+
+ Prep:
+ stage: Stage1
+ script: exit 0
+ when: manual
+
+ Build:
+ stage: Stage2
+ needs: ['Prep']
+ script: exit 0
+ parallel: 6
+
+ Test:
+ stage: Stage3
+ needs: ['Build']
+ script: exit 0
+
+ Deploy:
+ stage: Stage3
+ needs: ['Test']
+ script: exit 0
+ parallel: 6
+ YAML
+ }
+ ]
+ )
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ Flow::Pipeline.visit_latest_pipeline(pipeline_condition: 'skipped')
+ end
+
+ after do
+ runner&.remove_via_api!
+ project&.remove_via_api!
+ end
+
+ it 'does not leave any job in skipped state', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349158' do
+ Page::Project::Pipeline::Show.perform do |show|
+ show.click_job_action('Prep') # Trigger pipeline manually
+
+ show.wait_until(max_duration: 300, sleep_interval: 2, reload: false) do
+ project.pipelines.last[:status] == 'success'
+ end
+
+ aggregate_failures do
+ expect(show).to have_build('Test', status: :success)
+
+ show.click_job_dropdown('Build')
+ expect(show).not_to have_skipped_job_in_group
+
+ show.click_job_dropdown('Build') # Close Build dropdown
+ show.click_job_dropdown('Deploy')
+ expect(show).not_to have_skipped_job_in_group
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
index 5e0f1911811..9a5a5416d5c 100644
--- a/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb
@@ -3,6 +3,8 @@
module QA
RSpec.describe 'Package', :orchestrated, :registry, only: { pipeline: :main } do
describe 'Dependency Proxy' do
+ using RSpec::Parameterized::TableSyntax
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'dependency-proxy-project'
@@ -40,12 +42,13 @@ module QA
runner.remove_via_api!
end
- where(:docker_client_version) do
- %w[docker:19.03.12 docker:20.10]
+ where(:case_name, :docker_client_version, :testcase) do
+ 'using docker:19.03.12' | 'docker:19.03.12' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347605'
+ 'using docker:20.10' | 'docker:20.10' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347604'
end
with_them do
- it "pulls an image using the dependency proxy" do
+ it "pulls an image using the dependency proxy", testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
@@ -58,7 +61,7 @@ module QA
image: "#{docker_client_version}"
services:
- name: "#{docker_client_version}-dind"
- command: ["--insecure-registry=gitlab.test:80"]
+ command: ["--insecure-registry=gitlab.test:80"]
before_script:
- apk add curl jq grep
- echo $CI_DEPENDENCY_PROXY_SERVER
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
index 92e4d64fee4..2da0f6a0cf8 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb
@@ -32,55 +32,22 @@ module QA
"#{uri.scheme}://#{uri.host}:#{uri.port}"
end
- let(:composer_json_file) do
- <<~EOF
- {
- "name": "#{project.path_with_namespace}/#{package.name}",
- "description": "Library XY",
- "type": "library",
- "license": "GPL-3.0-only",
- "authors": [
- {
- "name": "John Doe",
- "email": "john@example.com"
- }
- ],
- "require": {}
- }
- EOF
- end
-
- let(:gitlab_ci_yaml) do
- <<~YAML
- publish:
- image: curlimages/curl:latest
- stage: build
- variables:
- URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN"
- script:
- - version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG")
- - insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "")
- - response=$(curl -s -w "%{http_code}" $insecure --data $version $URL)
- - code=$(echo "$response" | tail -n 1)
- - body=$(echo "$response" | head -n 1)
- tags:
- - "runner-for-#{project.name}"
- YAML
- end
-
before do
Flow::Login.sign_in
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ composer_yaml = ERB.new(read_fixture('package_managers/composer', 'composer_upload_package.yaml.erb')).result(binding)
+ composer_json = ERB.new(read_fixture('package_managers/composer', 'composer.json.erb')).result(binding)
+
commit.project = project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([{
file_path: '.gitlab-ci.yml',
- content: gitlab_ci_yaml
+ content: composer_yaml
},
{
file_path: 'composer.json',
- content: composer_json_file
+ content: composer_json
}]
)
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb
index 15578cd5e6b..22495796605 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
@@ -46,25 +46,13 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ conan_yaml = ERB.new(read_fixture('package_managers/conan', 'conan_upload_install_package.yaml.erb')).result(binding)
+
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([{
file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: conanio/gcc7
-
- test_package:
- stage: deploy
- script:
- - "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan"
- - "conan new #{package.name}/0.1 -t"
- - "conan create . mycompany/stable"
- - "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}"
- YAML
+ content: conan_yaml
}])
end
end
@@ -90,8 +78,10 @@ module QA
Page::Project::Packages::Show.perform(&:click_delete)
Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content("Package deleted successfully")
- expect(index).not_to have_package(package.name)
+ aggregate_failures 'package deletion' do
+ expect(index).to have_content("Package deleted successfully")
+ expect(index).not_to have_package(package.name)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb
index ded90607d67..71acc3a8f92 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
@@ -3,6 +3,8 @@
module QA
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'Generic Repository' do
+ include Runtime::Fixtures
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'generic-package-project'
@@ -25,29 +27,6 @@ module QA
end
end
- let(:gitlab_ci_yaml) do
- <<~YAML
- image: curlimages/curl:latest
-
- stages:
- - upload
- - download
-
- upload:
- stage: upload
- script:
- - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file file.txt ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/#{package.name}/0.0.1/file.txt'
- tags:
- - "runner-for-#{project.name}"
- download:
- stage: download
- script:
- - 'wget --header="JOB-TOKEN: $CI_JOB_TOKEN" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/#{package.name}/0.0.1/file.txt -O file_downloaded.txt'
- tags:
- - "runner-for-#{project.name}"
- YAML
- end
-
let(:file_txt) do
<<~EOF
Hello, world!
@@ -59,11 +38,13 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ generic_packages_yaml = ERB.new(read_fixture('package_managers/generic', 'generic_upload_install_package.yaml.erb')).result(binding)
+
commit.project = project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([{
file_path: '.gitlab-ci.yml',
- content: gitlab_ci_yaml
+ content: generic_packages_yaml
},
{
file_path: 'file.txt',
@@ -100,21 +81,11 @@ module QA
package.remove_via_api!
end
- it 'uploads a generic package, downloads and deletes it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348017' do
+ it 'uploads a generic package and downloads it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348017' do
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package.name)
- index.click_package(package.name)
- end
-
- Page::Project::Packages::Show.perform(&:click_delete)
-
- Page::Project::Packages::Index.perform do |index|
- aggregate_failures 'package deletion' do
- expect(index).to have_content("Package deleted successfully")
- expect(index).to have_no_package(package.name)
- end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb
index 92d0f547764..d2e816f9bf9 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
@@ -3,6 +3,7 @@
module QA
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'Helm Registry' do
+ using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
include_context 'packages registry qa scenario'
@@ -10,140 +11,105 @@ module QA
let(:package_version) { '1.3.7' }
let(:package_type) { 'helm' }
- let(:package_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- deploy:
- image: alpine:3
- script:
- - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
- - apk add curl
- - helm create #{package_name}
- - cp ./Chart.yaml #{package_name}
- - helm package #{package_name}
- - http_code=$(curl --write-out "%{http_code}" --request POST --form 'chart=@#{package_name}-#{package_version}.tgz' --user #{username}:#{access_token} ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts --output /dev/null --silent)
- - '[ $http_code = "201" ]'
- only:
- - "#{package_project.default_branch}"
- tags:
- - "runner-for-#{package_project.group.name}"
- YAML
- }
+ where(:case_name, :authentication_token_type, :testcase) do
+ 'using personal access token' | :personal_access_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347586'
+ 'using ci job token' | :ci_job_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347587'
+ 'using project deploy token' | :project_deploy_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347588'
end
- let(:package_chart_yaml_file) do
- {
- file_path: "Chart.yaml",
- content:
- <<~EOF
- apiVersion: v2
- name: #{package_name}
- description: GitLab QA helm package
- type: application
- version: #{package_version}
- appVersion: "1.16.0"
- EOF
- }
- end
-
- let(:client_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- pull:
- image: alpine:3
- script:
- - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing
- - helm repo add --username #{username} --password #{access_token} gitlab_qa ${CI_API_V4_URL}/projects/#{package_project.id}/packages/helm/stable
- - helm repo update
- - helm pull gitlab_qa/#{package_name}
- only:
- - "#{client_project.default_branch}"
- tags:
- - "runner-for-#{client_project.group.name}"
- YAML
- }
- end
-
- %i[personal_access_token ci_job_token project_deploy_token].each do |authentication_token_type|
- context "using a #{authentication_token_type}" do
- let(:username) do
- case authentication_token_type
- when :personal_access_token
- Runtime::User.username
- when :ci_job_token
- 'gitlab-ci-token'
- when :project_deploy_token
- project_deploy_token.username
- end
+ with_them do
+ let(:username) do
+ case authentication_token_type
+ when :personal_access_token
+ Runtime::User.username
+ when :ci_job_token
+ 'gitlab-ci-token'
+ when :project_deploy_token
+ project_deploy_token.username
end
+ end
- let(:access_token) do
- case authentication_token_type
- when :personal_access_token
- personal_access_token
- when :ci_job_token
- '${CI_JOB_TOKEN}'
- when :project_deploy_token
- project_deploy_token.token
- end
+ let(:access_token) do
+ case authentication_token_type
+ when :personal_access_token
+ personal_access_token
+ when :ci_job_token
+ '${CI_JOB_TOKEN}'
+ when :project_deploy_token
+ project_deploy_token.token
end
+ end
- it "pushes and pulls a helm chart" do
- Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.project = package_project
- commit.commit_message = 'Add .gitlab-ci.yml'
- commit.add_files([package_gitlab_ci_file, package_chart_yaml_file])
- end
+ it "pushes and pulls a helm chart", testcase: params[:testcase] do
+ Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding)
+ helm_chart_yaml = ERB.new(read_fixture('package_managers/helm', 'Chart.yaml.erb')).result(binding)
+
+ commit.project = package_project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files([
+ {
+ file_path: '.gitlab-ci.yml',
+ content: helm_upload_yaml
+ },
+ {
+ file_path: 'Chart.yaml',
+ content: helm_chart_yaml
+ }
+ ])
end
+ end
- package_project.visit!
+ package_project.visit!
- Flow::Pipeline.visit_latest_pipeline
+ Flow::Pipeline.visit_latest_pipeline
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.click_job('deploy')
- end
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('deploy')
+ end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
- end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
- Page::Project::Menu.perform(&:click_packages_link)
+ Page::Project::Menu.perform(&:click_packages_link)
- Page::Project::Packages::Index.perform do |index|
- expect(index).to have_package(package_name)
+ Page::Project::Packages::Index.perform do |index|
+ expect(index).to have_package(package_name)
- index.click_package(package_name)
- end
+ index.click_package(package_name)
+ end
- Page::Project::Packages::Show.perform do |show|
- expect(show).to have_package_info(package_name, package_version)
- end
+ Page::Project::Packages::Show.perform do |show|
+ expect(show).to have_package_info(package_name, package_version)
+ end
- Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.project = client_project
- commit.commit_message = 'Add .gitlab-ci.yml'
- commit.add_files([client_gitlab_ci_file])
- end
+ Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ helm_install_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_install_package.yaml.erb')).result(binding)
+
+ commit.project = client_project
+ commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.add_files([
+ {
+ file_path: '.gitlab-ci.yml',
+ content: helm_install_yaml
+ }
+ ])
end
+ end
- client_project.visit!
+ client_project.visit!
- Flow::Pipeline.visit_latest_pipeline
+ Flow::Pipeline.visit_latest_pipeline
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.click_job('pull')
- end
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('pull')
+ end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 800)
- end
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb
index 57e1aa6a087..45693ecee41 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb
@@ -13,80 +13,10 @@ module QA
let(:package_version) { '1.3.7' }
let(:package_type) { 'maven_gradle' }
- let(:package_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- deploy:
- image: gradle:6.5-jdk11
- script:
- - 'gradle publish'
- only:
- - "#{package_project.default_branch}"
- tags:
- - "runner-for-#{package_project.group.name}"
- YAML
- }
- end
-
- let(:package_build_gradle_file) do
- {
- file_path: 'build.gradle',
- content:
- <<~EOF
- plugins {
- id 'java'
- id 'maven-publish'
- }
-
- publishing {
- publications {
- library(MavenPublication) {
- groupId '#{group_id}'
- artifactId '#{artifact_id}'
- version '#{package_version}'
- from components.java
- }
- }
- repositories {
- maven {
- url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven"
- credentials(HttpHeaderCredentials) {
- name = "Private-Token"
- value = "#{personal_access_token}"
- }
- authentication {
- header(HttpHeaderAuthentication)
- }
- }
- }
- }
- EOF
- }
- end
-
- let(:client_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- build:
- image: gradle:6.5-jdk11
- script:
- - 'gradle build'
- only:
- - "#{client_project.default_branch}"
- tags:
- - "runner-for-#{client_project.group.name}"
- YAML
- }
- end
-
- where(:authentication_token_type, :maven_header_name) do
- :personal_access_token | 'Private-Token'
- :ci_job_token | 'Job-Token'
- :project_deploy_token | 'Deploy-Token'
+ where(:case_name, :authentication_token_type, :maven_header_name, :testcase) do
+ 'using personal access token' | :personal_access_token | 'Private-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347601'
+ 'using ci job token' | :ci_job_token | 'Job-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347603'
+ 'using project deploy token' | :project_deploy_token | 'Deploy-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347602'
end
with_them do
@@ -101,49 +31,24 @@ module QA
end
end
- let(:client_build_gradle_file) do
- {
- file_path: 'build.gradle',
- content:
- <<~EOF
- plugins {
- id 'java'
- id 'application'
- }
-
- repositories {
- jcenter()
- maven {
- url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven"
- name "GitLab"
- credentials(HttpHeaderCredentials) {
- name = '#{maven_header_name}'
- value = #{token}
- }
- authentication {
- header(HttpHeaderAuthentication)
- }
- }
- }
-
- dependencies {
- implementation group: '#{group_id}', name: '#{artifact_id}', version: '#{package_version}'
- testImplementation 'junit:junit:4.12'
- }
-
- application {
- mainClassName = 'gradle_maven_app.App'
- }
- EOF
- }
- end
-
- it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}" do
+ it 'pushes and pulls a maven package via gradle', testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_upload_package.yaml.erb')).result(binding)
+ build_upload_gradle = ERB.new(read_fixture('package_managers/maven', 'build_upload.gradle.erb')).result(binding)
+
commit.project = package_project
commit.commit_message = 'Add .gitlab-ci.yml'
- commit.add_files([package_gitlab_ci_file, package_build_gradle_file])
+ commit.add_files([
+ {
+ file_path: '.gitlab-ci.yml',
+ content: gradle_upload_yaml
+ },
+ {
+ file_path: 'build.gradle',
+ content: build_upload_gradle
+ }
+ ])
end
end
@@ -173,9 +78,21 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ gradle_install_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_install_package.yaml.erb')).result(binding)
+ build_install_gradle = ERB.new(read_fixture('package_managers/maven', 'build_install.gradle.erb')).result(binding)
+
commit.project = client_project
- commit.commit_message = 'Add .gitlab-ci.yml'
- commit.add_files([client_gitlab_ci_file, client_build_gradle_file])
+ commit.commit_message = 'Add files'
+ commit.add_files([
+ {
+ file_path: '.gitlab-ci.yml',
+ content: gradle_install_yaml
+ },
+ {
+ file_path: 'build.gradle',
+ content: build_install_gradle
+ }
+ ])
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb
index e6591b6adb9..b4ebb9dd475 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb
@@ -13,121 +13,6 @@ module QA
let(:package_version) { '1.3.7' }
let(:package_type) { 'maven' }
- let(:package_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- deploy:
- image: maven:3.6-jdk-11
- script:
- - 'mvn deploy -s settings.xml'
- only:
- - "#{package_project.default_branch}"
- tags:
- - "runner-for-#{package_project.group.name}"
- YAML
- }
- end
-
- let(:package_pom_file) do
- {
- file_path: 'pom.xml',
- content: <<~XML
- <project>
- <groupId>#{group_id}</groupId>
- <artifactId>#{artifact_id}</artifactId>
- <version>#{package_version}</version>
- <modelVersion>4.0.0</modelVersion>
- <repositories>
- <repository>
- <id>#{package_project.name}</id>
- <url>#{gitlab_address_with_port}/api/v4/groups/#{package_project.group.id}/-/packages/maven</url>
- </repository>
- </repositories>
- <distributionManagement>
- <repository>
- <id>#{package_project.name}</id>
- <url>#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven</url>
- </repository>
- <snapshotRepository>
- <id>#{package_project.name}</id>
- <url>#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven</url>
- </snapshotRepository>
- </distributionManagement>
- </project>
- XML
- }
- end
-
- let(:client_gitlab_ci_file) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- install:
- image: maven:3.6-jdk-11
- script:
- - "mvn install -s settings.xml"
- only:
- - "#{client_project.default_branch}"
- tags:
- - "runner-for-#{client_project.group.name}"
- YAML
- }
- end
-
- let(:client_pom_file) do
- {
- file_path: 'pom.xml',
- content: <<~XML
- <project>
- <groupId>#{group_id}</groupId>
- <artifactId>maven_client</artifactId>
- <version>1.0</version>
- <modelVersion>4.0.0</modelVersion>
- <repositories>
- <repository>
- <id>#{package_project.name}</id>
- <url>#{gitlab_address_with_port}/api/v4/groups/#{package_project.group.id}/-/packages/maven</url>
- </repository>
- </repositories>
- <dependencies>
- <dependency>
- <groupId>#{group_id}</groupId>
- <artifactId>#{artifact_id}</artifactId>
- <version>#{package_version}</version>
- </dependency>
- </dependencies>
- </project>
- XML
- }
- end
-
- let(:settings_xml_with_pat) do
- {
- file_path: 'settings.xml',
- content: <<~XML
- <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
- <servers>
- <server>
- <id>#{package_project.name}</id>
- <configuration>
- <httpHeaders>
- <property>
- <name>Private-Token</name>
- <value>#{personal_access_token}</value>
- </property>
- </httpHeaders>
- </configuration>
- </server>
- </servers>
- </settings>
- XML
- }
- end
-
where(:authentication_token_type, :maven_header_name) do
:personal_access_token | 'Private-Token'
:ci_job_token | 'Job-Token'
@@ -146,39 +31,28 @@ module QA
end
end
- let(:settings_xml) do
- {
- file_path: 'settings.xml',
- content: <<~XML
- <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
- <servers>
- <server>
- <id>#{package_project.name}</id>
- <configuration>
- <httpHeaders>
- <property>
- <name>#{maven_header_name}</name>
- <value>#{token}</value>
- </property>
- </httpHeaders>
- </configuration>
- </server>
- </servers>
- </settings>
- XML
- }
- end
-
it "pushes and pulls a maven package via maven using #{params[:authentication_token_type]}" do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ maven_upload_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_upload_package.yaml.erb')).result(binding)
+ package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding)
+ settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding)
+
commit.project = package_project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([
- package_gitlab_ci_file,
- package_pom_file,
- settings_xml
+ {
+ file_path: '.gitlab-ci.yml',
+ content: maven_upload_package_yaml
+ },
+ {
+ file_path: 'pom.xml',
+ content: package_pom_xml
+ },
+ {
+ file_path: 'settings.xml',
+ content: settings_xml
+ }
])
end
end
@@ -209,12 +83,25 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ maven_install_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_install_package.yaml.erb')).result(binding)
+ client_pom_xml = ERB.new(read_fixture('package_managers/maven', 'client_pom.xml.erb')).result(binding)
+ settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding)
+
commit.project = client_project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([
- client_gitlab_ci_file,
- client_pom_file,
- settings_xml
+ {
+ file_path: '.gitlab-ci.yml',
+ content: maven_install_package_yaml
+ },
+ {
+ file_path: 'pom.xml',
+ content: client_pom_xml
+ },
+ {
+ file_path: 'settings.xml',
+ content: settings_xml
+ }
])
end
end
@@ -278,7 +165,19 @@ module QA
end
def create_duplicated_package
- with_fixtures([package_pom_file, settings_xml_with_pat]) do |dir|
+ settings_xml_with_pat = ERB.new(read_fixture('package_managers/maven', 'settings_with_pat.xml.erb')).result(binding)
+ package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding)
+
+ with_fixtures([
+ {
+ file_path: 'pom.xml',
+ content: package_pom_xml
+ },
+ {
+ file_path: 'settings.xml',
+ content: settings_xml_with_pat
+ }
+ ]) do |dir|
Service::DockerRun::Maven.new(dir).publish!
end
@@ -294,12 +193,25 @@ module QA
def push_duplicated_package
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ maven_upload_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_upload_package.yaml.erb')).result(binding)
+ package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding)
+ settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding)
+
commit.project = client_project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([
- package_gitlab_ci_file,
- package_pom_file,
- settings_xml
+ {
+ file_path: '.gitlab-ci.yml',
+ content: maven_upload_package_yaml
+ },
+ {
+ file_path: 'pom.xml',
+ content: package_pom_xml
+ },
+ {
+ file_path: 'settings.xml',
+ content: settings_xml
+ }
])
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
index 70b31c1beca..04aaefbaf5c 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package Registry', :orchestrated, :packages, :object_storage do
+ RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do
describe 'npm instance level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
@@ -50,79 +50,10 @@ module QA
runner.name = "qa-runner-#{Time.now.to_i}"
runner.tags = ["runner-for-#{project.group.name}"]
runner.executor = :docker
- runner.token = project.group.runners_token
+ runner.token = project.group.reload!.runners_token
end
end
- let(:gitlab_ci_deploy_yaml) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: node:latest
-
- stages:
- - deploy
-
- deploy:
- stage: deploy
- script:
- - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=#{auth_token}">.npmrc
- - npm publish
- only:
- - "#{project.default_branch}"
- tags:
- - "runner-for-#{project.group.name}"
- YAML
- }
- end
-
- let(:gitlab_ci_install_yaml) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: node:latest
-
- stages:
- - install
-
- install:
- stage: install
- script:
- - "npm config set @#{registry_scope}:registry #{gitlab_address_with_port}/api/v4/packages/npm/"
- - "npm install #{package.name}"
- cache:
- key: ${CI_BUILD_REF_NAME}
- paths:
- - node_modules/
- artifacts:
- paths:
- - node_modules/
- only:
- - "#{another_project.default_branch}"
- tags:
- - "runner-for-#{another_project.group.name}"
- YAML
- }
- end
-
- let(:package_json) do
- {
- file_path: 'package.json',
- content: <<~JSON
- {
- "name": "#{package.name}",
- "version": "1.0.0",
- "description": "Example package for GitLab npm registry",
- "publishConfig": {
- "@#{registry_scope}:registry": "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/npm/"
- }
- }
- JSON
- }
- end
-
let(:package) do
Resource::Package.init do |package|
package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}"
@@ -137,10 +68,10 @@ module QA
another_project.remove_via_api!
end
- where(:authentication_token_type, :token_name) do
- :personal_access_token | 'Personal Access Token'
- :ci_job_token | 'CI Job Token'
- :project_deploy_token | 'Deploy Token'
+ where(:case_name, :authentication_token_type, :token_name, :testcase) do
+ 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347600'
+ 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347599'
+ 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347598'
end
with_them do
@@ -155,14 +86,23 @@ module QA
end
end
- it "push and pull a npm package via CI using a #{params[:token_name]}" do
+ it 'push and pull a npm package via CI', testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
+ npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding)
+ package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding)
+
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([
- gitlab_ci_deploy_yaml,
- package_json
+ {
+ file_path: '.gitlab-ci.yml',
+ content: npm_upload_yaml
+ },
+ {
+ file_path: 'package.json',
+ content: package_json
+ }
])
end
end
@@ -180,10 +120,15 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ npm_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_install_package_instance.yaml.erb')).result(binding)
+
commit.project = another_project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([
- gitlab_ci_install_yaml
+ {
+ file_path: '.gitlab-ci.yml',
+ content: npm_install_yaml
+ }
])
end
end
@@ -217,13 +162,6 @@ module QA
Page::Project::Packages::Show.perform do |show|
expect(show).to have_package_info(package.name, "1.0.0")
-
- show.click_delete
- end
-
- Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content("Package deleted successfully")
- expect(index).not_to have_package(package.name)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
index e25a742493b..cad1802f3e9 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package Registry', :orchestrated, :packages, :object_storage do
+ RSpec.describe 'Package Registry', :orchestrated, :reliable, :packages, :object_storage do
describe 'npm project level endpoint' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
@@ -46,62 +46,6 @@ module QA
end
end
- let(:gitlab_ci_yaml) do
- {
- file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: node:latest
-
- stages:
- - deploy
- - install
-
- deploy:
- stage: deploy
- script:
- - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=#{auth_token}">.npmrc
- - npm publish
- only:
- - "#{project.default_branch}"
- tags:
- - "runner-for-#{project.name}"
- install:
- stage: install
- script:
- - "npm config set @#{registry_scope}:registry #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
- - "npm install #{package.name}"
- cache:
- key: ${CI_BUILD_REF_NAME}
- paths:
- - node_modules/
- artifacts:
- paths:
- - node_modules/
- only:
- - "#{project.default_branch}"
- tags:
- - "runner-for-#{project.name}"
- YAML
- }
- end
-
- let(:package_json) do
- {
- file_path: 'package.json',
- content: <<~JSON
- {
- "name": "#{package.name}",
- "version": "1.0.0",
- "description": "Example package for GitLab npm registry",
- "publishConfig": {
- "@#{registry_scope}:registry": "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/npm/"
- }
- }
- JSON
- }
- end
-
let(:package) do
Resource::Package.init do |package|
package.name = "@#{registry_scope}/mypackage-#{SecureRandom.hex(8)}"
@@ -115,10 +59,10 @@ module QA
project.remove_via_api!
end
- where(:authentication_token_type, :token_name) do
- :personal_access_token | 'Personal Access Token'
- :ci_job_token | 'CI Job Token'
- :project_deploy_token | 'Deploy Token'
+ where(:case_name, :authentication_token_type, :token_name, :testcase) do
+ 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347592'
+ 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347594'
+ 'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347593'
end
with_them do
@@ -133,13 +77,22 @@ module QA
end
end
- it "push and pull a npm package via CI using a #{params[:token_name]}" do
+ it 'push and pull a npm package via CI', testcase: params[:testcase] do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding)
+ package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding)
+
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.add_files([
- gitlab_ci_yaml,
- package_json
+ {
+ file_path: '.gitlab-ci.yml',
+ content: npm_upload_install_yaml
+ },
+ {
+ file_path: 'package.json',
+ content: package_json
+ }
])
end
@@ -182,13 +135,6 @@ module QA
Page::Project::Packages::Show.perform do |show|
expect(show).to have_package_info(package.name, "1.0.0")
-
- show.click_delete
- end
-
- Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content("Package deleted successfully")
- expect(index).not_to have_package(package.name)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb
index d63bf486f11..24f83bc19fb 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb
@@ -5,6 +5,7 @@ module QA
describe 'NuGet Repository' do
using RSpec::Parameterized::TableSyntax
include Runtime::Fixtures
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'nuget-package-project'
@@ -53,7 +54,7 @@ module QA
runner.name = "qa-runner-#{Time.now.to_i}"
runner.tags = ["runner-for-#{project.group.name}"]
runner.executor = :docker
- runner.token = project.group.runners_token
+ runner.token = project.group.reload!.runners_token
end
end
@@ -62,10 +63,10 @@ module QA
package.remove_via_api!
end
- where(:authentication_token_type, :token_name) do
- :personal_access_token | 'Personal Access Token'
- :ci_job_token | 'CI Job Token'
- :group_deploy_token | 'Deploy Token'
+ where(:case_name, :authentication_token_type, :token_name, :testcase) do
+ 'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347597'
+ 'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347595'
+ 'using group deploy token' | :group_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347596'
end
with_them do
@@ -91,36 +92,19 @@ module QA
end
end
- it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}" do
+ it 'publishes a nuget package at the project endpoint and installs it from the group endpoint', testcase: params[:testcase] do
Flow::Login.sign_in
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ nuget_upload_yaml = ERB.new(read_fixture('package_managers/nuget', 'nuget_upload_package.yaml.erb')).result(binding)
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
commit.update_files(
[
{
file_path: '.gitlab-ci.yml',
- content: <<~YAML
- image: mcr.microsoft.com/dotnet/sdk:5.0
-
- stages:
- - deploy
-
- deploy:
- stage: deploy
- script:
- - dotnet restore -p:Configuration=Release
- - dotnet build -c Release
- - dotnet pack -c Release -p:PackageID=#{package.name}
- - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username #{auth_token_username} --password #{auth_token_password} --store-password-in-clear-text
- - dotnet nuget push "bin/Release/*.nupkg" --source gitlab
- rules:
- - if: '$CI_COMMIT_BRANCH == "#{project.default_branch}"'
- tags:
- - "runner-for-#{project.group.name}"
- YAML
+ content: nuget_upload_yaml
}
]
)
@@ -142,6 +126,8 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ nuget_install_yaml = ERB.new(read_fixture('package_managers/nuget', 'nuget_install_package.yaml.erb')).result(binding)
+
commit.project = another_project
commit.commit_message = 'Add new csproj file'
commit.add_files(
@@ -165,23 +151,7 @@ module QA
[
{
file_path: '.gitlab-ci.yml',
- content: <<~YAML
- image: mcr.microsoft.com/dotnet/sdk:5.0
-
- stages:
- - install
-
- install:
- stage: install
- script:
- - dotnet nuget locals all --clear
- - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username #{auth_token_username} --password #{auth_token_password} --store-password-in-clear-text
- - "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0"
- only:
- - "#{another_project.default_branch}"
- tags:
- - "runner-for-#{project.group.name}"
- YAML
+ content: nuget_install_yaml
}
]
)
@@ -204,14 +174,6 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package.name)
- index.click_package(package.name)
- end
-
- Page::Project::Packages::Show.perform(&:click_delete)
-
- Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content('Package deleted successfully')
- expect(index).not_to have_package(package.name)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
index 2e7bd8fc5d7..a0c2eca5bd2 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb
@@ -4,6 +4,7 @@ module QA
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'PyPI Repository' do
include Runtime::Fixtures
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'pypi-package-project'
@@ -36,56 +37,18 @@ module QA
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ pypi_yaml = ERB.new(read_fixture('package_managers/pypi', 'pypi_upload_install_package.yaml.erb')).result(binding)
+ pypi_setup_file = ERB.new(read_fixture('package_managers/pypi', 'setup.py.erb')).result(binding)
+
commit.project = project
- commit.commit_message = 'Add .gitlab-ci.yml'
+ commit.commit_message = 'Add files'
commit.add_files([{
file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: python:latest
- stages:
- - run
- - install
-
- run:
- stage: run
- script:
- - pip install twine
- - python setup.py sdist bdist_wheel
- - "TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*"
- tags:
- - "runner-for-#{project.name}"
- install:
- stage: install
- script:
- - "pip install #{package.name} --no-deps --index-url #{uri.scheme}://#{personal_access_token}:#{personal_access_token}@#{gitlab_host_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple --trusted-host #{gitlab_host_with_port}"
- tags:
- - "runner-for-#{project.name}"
-
- YAML
+ content: pypi_yaml
},
{
file_path: 'setup.py',
- content:
- <<~EOF
- import setuptools
-
- setuptools.setup(
- name="#{package.name}",
- version="0.0.1",
- author="Example Author",
- author_email="author@example.com",
- description="A small example package",
- packages=setuptools.find_packages(),
- classifiers=[
- "Programming Language :: Python :: 3",
- "License :: OSI Approved :: MIT License",
- "Operating System :: OS Independent",
- ],
- python_requires='>=3.6',
- )
- EOF
-
+ content: pypi_setup_file
}])
end
end
@@ -119,21 +82,11 @@ module QA
end
context 'when at the project level' do
- it 'publishes and installs a pypi package and deletes it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348015' do
+ it 'publishes and installs a pypi package', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348015' do
Page::Project::Menu.perform(&:click_packages_link)
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package.name)
- index.click_package(package.name)
- end
-
- Page::Project::Packages::Show.perform(&:click_delete)
-
- Page::Project::Packages::Index.perform do |index|
- aggregate_failures do
- expect(index).to have_content("Package deleted successfully")
- expect(index).not_to have_package(package.name)
- end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
index 062d2b49deb..b2208dc644c 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb
@@ -43,35 +43,21 @@ module QA
project.remove_via_api!
end
- it 'publishes and deletes a Ruby gem', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347649' do
+ it 'publishes a Ruby gem', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347649' do
Flow::Login.sign_in
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
+ rubygem_upload_yaml = ERB.new(read_fixture('package_managers/rubygems', 'rubygems_upload_package.yaml.erb')).result(binding)
+ rubygem_package_gemspec = ERB.new(read_fixture('package_managers/rubygems', 'package.gemspec.erb')).result(binding)
+
commit.project = project
commit.commit_message = 'Add package files'
commit.add_files(
[
{
file_path: '.gitlab-ci.yml',
- content:
- <<~YAML
- image: ruby
-
- test_package:
- stage: deploy
- before_script:
- - mkdir ~/.gem
- - echo "---" > ~/.gem/credentials
- - |
- echo "#{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems: '${CI_JOB_TOKEN}'" >> ~/.gem/credentials
- - chmod 0600 ~/.gem/credentials
- script:
- - gem build #{package.name}
- - gem push #{package.name}-0.0.1.gem --host #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems
- tags:
- - "runner-for-#{project.name}"
- YAML
+ content: rubygem_upload_yaml
},
{
file_path: 'lib/hello_gem.rb',
@@ -86,49 +72,7 @@ module QA
},
{
file_path: "#{package.name}.gemspec",
- content:
- <<~RUBY
- # frozen_string_literal: true
-
- Gem::Specification.new do |s|
- s.name = '#{package.name}'
- s.authors = ['Tanuki Steve', 'Hal 9000']
- s.author = 'Tanuki Steve'
- s.version = '0.0.1'
- s.date = '2011-09-29'
- s.summary = 'this is a test package'
- s.files = ['lib/hello_gem.rb']
- s.require_paths = ['lib']
-
- s.description = 'A test package for GitLab.'
- s.email = 'tanuki@not_real.com'
- s.homepage = 'https://gitlab.com/ruby-co/my-package'
- s.license = 'MIT'
-
- s.metadata = {
- 'bug_tracker_uri' => 'https://gitlab.com/ruby-co/my-package/issues',
- 'changelog_uri' => 'https://gitlab.com/ruby-co/my-package/CHANGELOG.md',
- 'documentation_uri' => 'https://gitlab.com/ruby-co/my-package/docs',
- 'mailing_list_uri' => 'https://gitlab.com/ruby-co/my-package/mailme',
- 'source_code_uri' => 'https://gitlab.com/ruby-co/my-package'
- }
-
- s.bindir = 'bin'
- s.platform = Gem::Platform::RUBY
- s.post_install_message = 'Installed, thank you!'
- s.rdoc_options = ['--main']
- s.required_ruby_version = '>= 2.7.0'
- s.required_rubygems_version = '>= 1.8.11'
- s.requirements = 'A high powered server or calculator'
- s.rubygems_version = '1.8.09'
-
- s.add_dependency 'dependency_1', '~> 1.2.3'
- s.add_dependency 'dependency_2', '3.0.0'
- s.add_dependency 'dependency_3', '>= 1.0.0'
- s.add_dependency 'dependency_4'
- end
-
- RUBY
+ content: rubygem_package_gemspec
}
]
)
@@ -150,14 +94,6 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_package(package.name)
- index.click_package(package.name)
- end
-
- Page::Project::Packages::Show.perform(&:click_delete)
-
- Page::Project::Packages::Index.perform do |index|
- expect(index).to have_content("Package deleted successfully")
- expect(index).not_to have_package(package.name)
end
end
end
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 f4a5c715684..c86f75e0b16 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
@@ -33,13 +33,13 @@ module QA
end
keys = [
- [Runtime::Key::RSA, 8192],
- [Runtime::Key::ECDSA, 521],
- [Runtime::Key::ED25519]
+ ['https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348022', Runtime::Key::RSA, 8192],
+ ['https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348021', Runtime::Key::ECDSA, 521],
+ ['https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348020', Runtime::Key::ED25519]
]
- keys.each do |(key_class, bits)|
- it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do
+ keys.each do |(testcase, key_class, bits)|
+ it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines", testcase: testcase do
key = key_class.new(*bits)
Resource::DeployKey.fabricate_via_browser_ui! do |resource|
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
index 2538f249010..718dc9860fb 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb
@@ -3,6 +3,8 @@
module QA
RSpec.describe 'Configure' do
describe 'AutoDevOps Templates', only: { subdomain: :staging } do
+ using RSpec::Parameterized::TableSyntax
+
# specify jobs to be disabled in the pipeline.
# CANARY_ENABLED will allow the pipeline to be
# blocked by a manual job, rather than fail
@@ -17,8 +19,8 @@ module QA
]
end
- where(:template) do
- %w[express]
+ where(:case_name, :template, :testcase) do
+ 'using express template' | 'express' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348075'
end
with_them do
@@ -45,7 +47,7 @@ module QA
Flow::Login.sign_in
end
- it 'works with Auto DevOps' do
+ it 'works with Auto DevOps', testcase: params[:testcase] do
%w[build code_quality test].each do |job|
pipeline.visit!
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
index 738c99efb28..e58d70fdfb5 100644
--- a/qa/qa/specs/helpers/quarantine.rb
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -10,8 +10,10 @@ module QA
extend self
- # Skip tests in quarantine unless we explicitly focus on them.
+ # Skip tests in quarantine unless we explicitly focus on them or quarantine disabled
def skip_or_run_quarantined_tests_or_contexts(example)
+ return if Runtime::Env.quarantine_disabled?
+
if filters.key?(:quarantine)
included_filters = filters_other_than_quarantine
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 2c9e302fc56..a861c13a44c 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -7,6 +7,7 @@ module QA
module Specs
class Runner < Scenario::Template
attr_accessor :tty, :tags, :options
+ RegexMismatchError = Class.new(StandardError)
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
@@ -72,16 +73,48 @@ module QA
elsif Runtime::Scenario.attributes[:count_examples_only]
args.unshift('--dry-run')
out = StringIO.new
+
RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status|
abort if status.nonzero?
end
- $stdout.puts out.string.match(/(\d+) examples,/)[1]
+
+ begin
+ total_examples = out.string.match(/(\d+) examples?,/)[1]
+ rescue StandardError
+ raise RegexMismatchError, 'Rspec output did not match regex'
+ end
+
+ filename = build_filename
+
+ File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0
+
+ $stdout.puts "Total examples in #{Runtime::Scenario.klass}: #{total_examples}#{total_examples.to_i > 0 ? ". Saved to file: #{filename}" : ''}"
else
RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap do |status|
abort if status.nonzero?
end
end
end
+
+ private
+
+ def build_filename
+ filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase
+
+ tags = []
+ options.reduce do |before, after|
+ tags << after if %w[--tag -t].include?(before)
+ after
+ end
+ tags = tags.compact.join('_')
+
+ filename.concat("_#{tags}") unless tags.empty?
+
+ filename.concat('.txt')
+
+ FileUtils.mkdir_p('no_of_examples')
+ File.join('no_of_examples', filename)
+ end
end
end
end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 663761805ee..976188e45c6 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -44,6 +44,18 @@ module QA
end
end
+ def patch(url, payload = nil)
+ with_retry_on_too_many_requests do
+ RestClient::Request.execute(
+ method: :patch,
+ url: url,
+ payload: payload,
+ verify_ssl: false)
+ rescue RestClient::ExceptionWithResponse => e
+ return_response_or_raise(e)
+ end
+ end
+
def put(url, payload = nil)
with_retry_on_too_many_requests do
RestClient::Request.execute(
diff --git a/qa/qa/support/formatters/allure_metadata_formatter.rb b/qa/qa/support/formatters/allure_metadata_formatter.rb
index 10769ba5c57..da35ffde1ae 100644
--- a/qa/qa/support/formatters/allure_metadata_formatter.rb
+++ b/qa/qa/support/formatters/allure_metadata_formatter.rb
@@ -15,14 +15,42 @@ module QA
def example_started(example_notification)
example = example_notification.example
- quarantine_issue = example.metadata.dig(:quarantine, :issue)
- example.issue('Quarantine issue', quarantine_issue) if quarantine_issue
+ add_quarantine_issue_link(example)
+ add_failure_issues_link(example)
+ add_ci_job_link(example)
+ end
+
+ private
+
+ # Add quarantine issue links
+ #
+ # @param [RSpec::Core::Example] example
+ # @return [void]
+ def add_quarantine_issue_link(example)
+ issue_link = example.metadata.dig(:quarantine, :issue)
+
+ return unless issue_link
+ return example.issue('Quarantine issue', issue_link) if issue_link.is_a?(String)
+ return issue_link.each { |link| example.issue('Quarantine issue', link) } if issue_link.is_a?(Array)
+ end
+ # Add failure issues link
+ #
+ # @param [RSpec::Core::Example] example
+ # @return [void]
+ def add_failure_issues_link(example)
spec_file = example.file_path.split('/').last
example.issue(
'Failure issues',
"https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}"
)
+ end
+
+ # Add ci job link
+ #
+ # @param [RSpec::Core::Example] example
+ # @return [void]
+ def add_ci_job_link(example)
return unless Runtime::Env.running_in_ci?
example.add_link(name: "Job(#{Runtime::Env.ci_job_name})", url: Runtime::Env.ci_job_url)
diff --git a/qa/qa/support/formatters/test_stats_formatter.rb b/qa/qa/support/formatters/test_stats_formatter.rb
index 7678cb8406c..430294b0bb6 100644
--- a/qa/qa/support/formatters/test_stats_formatter.rb
+++ b/qa/qa/support/formatters/test_stats_formatter.rb
@@ -14,40 +14,38 @@ module QA
return log(:warn, 'Missing QA_INFLUXDB_URL, skipping metrics export!') unless influxdb_url
return log(:warn, 'Missing QA_INFLUXDB_TOKEN, skipping metrics export!') unless influxdb_token
- data = notification.examples.map { |example| test_stats(example) }.compact
- influx_client.create_write_api.write(data: data)
- log(:info, "Pushed #{data.length} entries to influxdb")
- rescue StandardError => e
- log(:error, "Failed to push data to influxdb, error: #{e}")
+ push_test_stats(notification.examples)
+ push_fabrication_stats
end
private
- # InfluxDb client
+ # Push test execution stats to influxdb
#
- # @return [InfluxDB2::Client]
- def influx_client
- @influx_client ||= InfluxDB2::Client.new(
- influxdb_url,
- influxdb_token,
- bucket: 'e2e-test-stats',
- org: 'gitlab-qa',
- precision: InfluxDB2::WritePrecision::NANOSECOND
- )
- end
+ # @param [Array<RSpec::Core::Example>] examples
+ # @return [void]
+ def push_test_stats(examples)
+ data = examples.map { |example| test_stats(example) }.compact
- # InfluxDb instance url
- #
- # @return [String]
- def influxdb_url
- @influxdb_url ||= env('QA_INFLUXDB_URL')
+ influx_client.write(data: data)
+ log(:debug, "Pushed #{data.length} test execution entries to influxdb")
+ rescue StandardError => e
+ log(:error, "Failed to push test execution stats to influxdb, error: #{e}")
end
- # Influxdb token
+ # Push resource fabrication stats to influxdb
#
- # @return [String]
- def influxdb_token
- @influxdb_token ||= env('QA_INFLUXDB_TOKEN')
+ # @return [void]
+ def push_fabrication_stats
+ data = Tools::TestResourceDataProcessor.resources.flat_map do |resource, values|
+ values.map { |v| fabrication_stats(resource: resource, **v) }
+ end
+ return if data.empty?
+
+ influx_client.write(data: data)
+ log(:debug, "Pushed #{data.length} resource fabrication entries to influxdb")
+ rescue StandardError => e
+ log(:error, "Failed to push fabrication stats to influxdb, error: #{e}")
end
# Transform example to influxdb compatible metrics data
@@ -73,7 +71,8 @@ module QA
job_name: job_name,
merge_request: merge_request,
run_type: env('QA_RUN_TYPE') || run_type,
- stage: devops_stage(file_path)
+ stage: devops_stage(file_path),
+ testcase: example.metadata[:testcase]
},
fields: {
id: example.id,
@@ -84,8 +83,7 @@ module QA
retry_attempts: example.metadata[:retry_attempts] || 0,
job_url: QA::Runtime::Env.ci_job_url,
pipeline_url: env('CI_PIPELINE_URL'),
- pipeline_id: env('CI_PIPELINE_ID'),
- testcase: example.metadata[:testcase]
+ pipeline_id: env('CI_PIPELINE_ID')
}
}
rescue StandardError => e
@@ -93,6 +91,34 @@ module QA
nil
end
+ # Resource fabrication data point
+ #
+ # @param [String] resource
+ # @param [String] info
+ # @param [Symbol] fabrication_method
+ # @param [Symbol] http_method
+ # @param [Integer] fabrication_time
+ # @return [Hash]
+ def fabrication_stats(resource:, info:, fabrication_method:, http_method:, fabrication_time:, timestamp:, **)
+ {
+ name: 'fabrication-stats',
+ time: time,
+ tags: {
+ resource: resource,
+ fabrication_method: fabrication_method,
+ http_method: http_method,
+ run_type: env('QA_RUN_TYPE') || run_type,
+ merge_request: merge_request
+ },
+ fields: {
+ fabrication_time: fabrication_time,
+ info: info,
+ job_url: QA::Runtime::Env.ci_job_url,
+ timestamp: timestamp
+ }
+ }
+ end
+
# Project name
#
# @return [String]
@@ -150,7 +176,7 @@ module QA
# @param [String] message
# @return [void]
def log(level, message)
- QA::Runtime::Logger.public_send(level, "influxdb exporter: #{message}")
+ QA::Runtime::Logger.public_send(level, "[influxdb exporter]: #{message}")
end
# Return non empty environment variable value
@@ -170,6 +196,33 @@ module QA
def devops_stage(file_path)
file_path.match(%r{\d{1,2}_(\w+)/})&.captures&.first
end
+
+ # InfluxDb client
+ #
+ # @return [InfluxDB2::WriteApi]
+ def influx_client
+ @influx_client ||= InfluxDB2::Client.new(
+ influxdb_url,
+ influxdb_token,
+ bucket: 'e2e-test-stats',
+ org: 'gitlab-qa',
+ precision: InfluxDB2::WritePrecision::NANOSECOND
+ ).create_write_api
+ end
+
+ # InfluxDb instance url
+ #
+ # @return [String]
+ def influxdb_url
+ @influxdb_url ||= env('QA_INFLUXDB_URL')
+ end
+
+ # Influxdb token
+ #
+ # @return [String]
+ def influxdb_token
+ @influxdb_token ||= env('QA_INFLUXDB_TOKEN')
+ end
end
end
end
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index f5299ed840d..b402639b843 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -4,6 +4,8 @@ module QA
module Support
module Page
module Logging
+ using Rainbow
+
def assert_no_element(name)
log("asserting no element :#{name}")
@@ -17,7 +19,7 @@ module QA
end
def scroll_to(selector, text: nil)
- msg = "scrolling to :#{selector}"
+ msg = "scrolling to :#{Rainbow(selector).underline.bright}"
msg += " with text: #{text}" if text
log(msg)
@@ -37,7 +39,7 @@ module QA
element = super
- log("found :#{name}") if element
+ log("found :#{Rainbow(name).underline.bright}")
element
end
@@ -47,7 +49,7 @@ module QA
elements = super
- log("found #{elements.size} :#{name}") if elements
+ log("found #{elements.size} :#{Rainbow(name).underline.bright}") if elements
elements
end
@@ -71,7 +73,7 @@ module QA
end
def click_element(name, page = nil, **kwargs)
- msg = ["clicking :#{name}"]
+ msg = ["clicking :#{Rainbow(name).underline.bright}"]
msg << ", expecting to be at #{page.class}" if page
msg << "with args #{kwargs}"
@@ -170,7 +172,7 @@ module QA
end
def log_has_element_or_not(method, name, found, **kwargs)
- msg = ["#{method} :#{name}"]
+ msg = ["#{method} :#{Rainbow(name).underline.bright}"]
msg << %Q(with text "#{kwargs[:text]}") if kwargs[:text]
msg << "class: #{kwargs[:class]}" if kwargs[:class]
msg << "(wait: #{kwargs[:wait] || Capybara.default_max_wait_time})"
diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb
index a4e8035f964..1b9aa809051 100644
--- a/qa/qa/support/repeater.rb
+++ b/qa/qa/support/repeater.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
-
require 'active_support/inflector'
+require 'rainbow/refinement'
module QA
module Support
module Repeater
+ using Rainbow
DEFAULT_MAX_WAIT_TIME = 60
RepeaterConditionExceededError = Class.new(RuntimeError)
@@ -39,7 +40,7 @@ module QA
QA::Runtime::Logger.debug(msg.join(' '))
end
- QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if log && max_attempts && attempts > 0
+ QA::Runtime::Logger.debug("Attempt number #{attempts + 1}".bg(:yellow).black) if log && max_attempts && attempts > 0
result = yield
if result
diff --git a/qa/qa/tools/delete_test_resources.rb b/qa/qa/tools/delete_test_resources.rb
index 917cb2fa992..6f63c56f736 100644
--- a/qa/qa/tools/delete_test_resources.rb
+++ b/qa/qa/tools/delete_test_resources.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-# This script reads from test_resources.txt file to collect data about resources to delete
-# Deletes all deletable resources that E2E tests created
-# Resource type: Sandbox, User, Fork and RSpec::Mocks::Double are not included
+# This script reads from test-resources JSON file to collect data about resources to delete
+# Filter out resources that cannot be deleted
+# Then deletes all deletable resources that E2E tests created
#
# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# When in CI also requires: QA_TEST_RESOURCES_FILE_PATTERN
@@ -13,7 +13,15 @@ module QA
class DeleteTestResources
include Support::API
- def initialize(file_pattern = nil)
+ IGNORED_RESOURCES = [
+ 'QA::Resource::PersonalAccessToken',
+ 'QA::Resource::CiVariable',
+ 'QA::Resource::Repository::Commit',
+ 'QA::EE::Resource::GroupIteration',
+ 'QA::EE::Resource::Settings::Elasticsearch'
+ ].freeze
+
+ def initialize(file_pattern = Runtime::Env.test_resources_created_filepath)
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
@@ -22,45 +30,58 @@ module QA
end
def run
- puts 'Deleting test created resources...'
-
- if Runtime::Env.running_in_ci?
- raise ArgumentError, 'Please provide QA_TEST_RESOURCES_FILE_PATTERN' unless ENV['QA_TEST_RESOURCES_FILE_PATTERN']
-
- Dir.glob(@file_pattern).each do |file|
- delete_resources(load_file(file))
- end
- else
- file = Runtime::Env.test_resources_created_filepath
- raise ArgumentError, "'#{file}' either does not exist or empty." if !File.exist?(file) || File.zero?(file)
-
- delete_resources(load_file(file))
+ failures = files.flat_map do |file|
+ resources = read_file(file)
+ filtered_resources = filter_resources(resources)
+ delete_resources(filtered_resources)
end
- puts "\nDone"
+ return puts "\nDone" if failures.empty?
+
+ puts "\nFailed to delete #{failures.size} resources:\n"
+ puts failures
end
private
- def load_file(json)
- JSON.parse(File.read(json))
+ def files
+ puts "Gathering JSON files...\n"
+ files = Dir.glob(@file_pattern)
+ abort("There is no file with this pattern #{@file_pattern}") if files.empty?
+
+ files.reject { |file| File.zero?(file) }
+
+ files
end
- def delete_resources(resources)
- failures = []
+ def read_file(file)
+ JSON.parse(File.read(file))
+ end
- resources.each_key do |type|
- next if resources[type].empty?
+ def filter_resources(resources)
+ puts "Filtering resources - Only keep deletable resources...\n"
- resources[type].each do |resource|
- next if resource_not_found?(resource['api_path'])
+ transformed_values = resources.transform_values! do |v|
+ v.reject do |attributes|
+ attributes['info'] == "with full_path 'gitlab-qa-sandbox-group'" ||
+ attributes['http_method'] == 'get' && !attributes['info']&.include?("with username 'qa-") ||
+ attributes['api_path'] == 'Cannot find resource API path'
+ end
+ end
+
+ transformed_values.reject! { |k, v| v.empty? || IGNORED_RESOURCES.include?(k) }
+ end
- msg = resource['info'] ? "#{type} - #{resource['info']}" : "#{type} at #{resource['api_path']}"
+ def delete_resources(resources)
+ resources.each_with_object([]) do |(key, value), failures|
+ value.each do |resource|
+ next if resource_not_found?(resource['api_path'])
+ msg = resource['info'] ? "#{key} - #{resource['info']}" : "#{key} at #{resource['api_path']}"
puts "\nDeleting #{msg}..."
delete_response = delete(Runtime::API::Request.new(@api_client, resource['api_path']).url)
- if delete_response.code == 202
+ if delete_response.code == 202 || delete_response.code == 204
print "\e[32m.\e[0m"
else
print "\e[31mF\e[0m"
@@ -68,17 +89,11 @@ module QA
end
end
end
-
- unless failures.empty?
- puts "\nFailed to delete #{failures.length} resources:\n"
- puts failures
- end
end
def resource_not_found?(api_path)
- get_response = get Runtime::API::Request.new(@api_client, api_path).url
-
- get_response.code.eql? 404
+ # if api path contains param "?hard_delete=<boolean>", remove it
+ get(Runtime::API::Request.new(@api_client, api_path.split('?').first).url).code.eql? 404
end
end
end
diff --git a/qa/qa/tools/initialize_gitlab_auth.rb b/qa/qa/tools/initialize_gitlab_auth.rb
index 86791f1f624..18e90f0d739 100644
--- a/qa/qa/tools/initialize_gitlab_auth.rb
+++ b/qa/qa/tools/initialize_gitlab_auth.rb
@@ -16,7 +16,7 @@ module QA
def run
Runtime::Scenario.define(:gitlab_address, address)
- puts "Signing in and creating the default password for the root user if it's not set already..."
+ QA::Runtime::Logger.info("Signing in and creating the default password for the root user if it's not set already...")
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
Flow::Login.sign_in
diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb
index b99b97c1ea6..27e54c2d8bf 100644
--- a/qa/qa/tools/reliable_report.rb
+++ b/qa/qa/tools/reliable_report.rb
@@ -58,7 +58,7 @@ module QA
{
title: "Reliable e2e test report",
description: report_issue_body,
- labels: "Quality,test,type::maintenance,reliable test report"
+ labels: "Quality,test,type::maintenance,reliable test report,automation:devops-mapping-disable"
},
headers: { "PRIVATE-TOKEN" => gitlab_access_token }
)
diff --git a/qa/qa/tools/test_resource_data_processor.rb b/qa/qa/tools/test_resource_data_processor.rb
index 78fb6ef6cd0..965919dc516 100644
--- a/qa/qa/tools/test_resource_data_processor.rb
+++ b/qa/qa/tools/test_resource_data_processor.rb
@@ -6,60 +6,81 @@
module QA
module Tools
class TestResourceDataProcessor
- @resources ||= Hash.new { |hsh, key| hsh[key] = [] }
+ include Singleton
+
+ def initialize
+ @resources = Hash.new { |hsh, key| hsh[key] = [] }
+ end
class << self
- # Ignoring rspec-mocks, sandbox, user and fork resources
- # TODO: Will need to figure out which user resources can be collected, ignore for now
- #
- # Collecting resources created in E2E tests
- # Data is a Hash of resources with keys as resource type (group, project, issue, etc.)
- # Each type contains an array of resource object (hash) of the same type
- # E.g: { "QA::Resource::Project": [ { info: 'foo', api_path: '/foo'}, {...} ] }
- def collect(resource, info)
- return if resource.api_response.nil? ||
- resource.is_a?(RSpec::Mocks::Double) ||
- resource.is_a?(Resource::Sandbox) ||
- resource.is_a?(Resource::User) ||
- resource.is_a?(Resource::Fork)
+ delegate :collect, :write_to_file, :resources, to: :instance
+ end
- api_path = if resource.respond_to?(:api_delete_path)
- resource.api_delete_path.gsub('%2F', '/')
- elsif resource.respond_to?(:api_get_path)
- resource.api_get_path.gsub('%2F', '/')
- else
- 'Cannot find resource API path'
- end
+ # @return [Hash<String, Array>]
+ attr_reader :resources
- type = resource.class.name
+ # Collecting resources created in E2E tests
+ # Data is a Hash of resources with keys as resource type (group, project, issue, etc.)
+ # Each type contains an array of resource object (hash) of the same type
+ # E.g: { "QA::Resource::Project": [ { info: 'foo', api_path: '/foo'}, {...} ] }
+ #
+ # @param [QA::Resource::Base] resource fabricated resource
+ # @param [String] info resource info
+ # @param [Symbol] method fabrication method, api or browser_ui
+ # @param [Integer] time fabrication time
+ # @return [Hash]
+ def collect(resource:, info:, fabrication_method:, fabrication_time:)
+ api_path = resource_api_path(resource)
+ type = resource.class.name
- @resources[type] << { info: info, api_path: api_path }
- end
+ resources[type] << {
+ info: info,
+ api_path: api_path,
+ fabrication_method: fabrication_method,
+ fabrication_time: fabrication_time,
+ http_method: resource.api_fabrication_http_method,
+ timestamp: Time.now.to_s
+ }
+ end
+
+ # If JSON file exists and not empty, read and load file content
+ # Merge what is saved in @resources into the content from file
+ # Overwrite file content with the new data hash
+ # Otherwise create file and write data hash to file for the first time
+ #
+ # @return [void]
+ def write_to_file
+ return if resources.empty?
- # If JSON file exists and not empty, read and load file content
- # Merge what is saved in @resources into the content from file
- # Overwrite file content with the new data hash
- # Otherwise create file and write data hash to file for the first time
- def write_to_file
- return if @resources.empty?
+ file = Pathname.new(Runtime::Env.test_resources_created_filepath)
+ FileUtils.mkdir_p(file.dirname)
- file = Runtime::Env.test_resources_created_filepath
- FileUtils.mkdir_p('tmp/')
- FileUtils.touch(file)
- data = nil
+ data = resources.deep_dup
+ # merge existing json if present
+ JSON.parse(File.read(file)).deep_merge!(data) { |key, val, other_val| val + other_val } if file.exist?
+
+ File.write(file, JSON.pretty_generate(data))
+ end
- if File.zero?(file)
- data = @resources
- else
- data = JSON.parse(File.read(file))
+ private
- @resources.each_pair do |key, val|
- data[key].nil? ? data[key] = val : val.each { |item| data[key] << item }
- end
- end
+ # Determine resource api path or return default value
+ # Some resources fabricated via UI can raise no attribute error
+ #
+ # @param [QA::Resource::Base] resource
+ # @return [String]
+ def resource_api_path(resource)
+ default = 'Cannot find resource API path'
- File.open(file, 'w') { |f| f.write(JSON.pretty_generate(data.each_value(&:uniq!))) }
+ if resource.respond_to?(:api_delete_path)
+ resource.api_delete_path.gsub('%2F', '/')
+ elsif resource.respond_to?(:api_get_path)
+ resource.api_get_path.gsub('%2F', '/')
+ else
+ default
end
+ rescue QA::Resource::Base::NoValueError
+ default
end
end
end
diff --git a/qa/qa/vendor/smocker/event_payload.rb b/qa/qa/vendor/smocker/event_payload.rb
new file mode 100644
index 00000000000..4bf154b76c2
--- /dev/null
+++ b/qa/qa/vendor/smocker/event_payload.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Smocker
+ class EventPayload
+ def initialize(hook_data)
+ @hook_data = hook_data
+ end
+
+ def raw
+ @hook_data
+ end
+
+ def event
+ raw[:object_kind]&.to_sym
+ end
+
+ def project_name
+ raw.dig(:project, :name)
+ end
+
+ def mr?
+ event == :merge_request
+ end
+
+ def issue?
+ event == :issue
+ end
+
+ def note?
+ event == :note
+ end
+
+ def push?
+ event == :push
+ end
+
+ def tag?
+ event == :tag
+ end
+
+ def wiki?
+ event == :wiki_page
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/smocker/history_response.rb b/qa/qa/vendor/smocker/history_response.rb
new file mode 100644
index 00000000000..53d5759ef8b
--- /dev/null
+++ b/qa/qa/vendor/smocker/history_response.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require_relative './event_payload'
+require 'time'
+
+module QA
+ module Vendor
+ module Smocker
+ class HistoryResponse
+ attr_reader :payload
+
+ def initialize(payload)
+ @payload = payload
+ end
+
+ # Smocker context including call counter
+ def context
+ payload[:context]
+ end
+
+ # Smocker request data
+ def request
+ payload[:request]
+ end
+
+ # @return [EventPayload] the request body as a webhook event
+ def as_hook_event
+ body = request&.dig(:body)
+ EventPayload.new body if body
+ end
+
+ # @return [Time] Time request was recieved
+ def received
+ date = request&.dig(:date)
+ Time.parse date if date
+ end
+
+ # Find time elapsed since <target>
+ #
+ # @param target [Time] target time
+ # @return [Integer] seconds elapsed since <target>
+ def elapsed(target)
+ (received.to_f - target.to_f).round if received
+ end
+
+ def response
+ payload[:response]
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/smocker/smocker_api.rb b/qa/qa/vendor/smocker/smocker_api.rb
new file mode 100644
index 00000000000..3f595b58886
--- /dev/null
+++ b/qa/qa/vendor/smocker/smocker_api.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module Smocker
+ class SmockerApi
+ include Scenario::Actable
+ include Support::API
+
+ DEFAULT_MOCK = <<~YAML
+ - request:
+ method: POST
+ path: /default
+ response:
+ headers:
+ Content-Type: application/json
+ body: '{}'
+ YAML
+
+ # @param wait [Integer] seconds to wait for server
+ # @yieldparam [SmockerApi] the api object ready for interaction
+ def self.init(**wait_args)
+ if @container.nil?
+ @container = Service::DockerRun::Smocker.new
+ @container.register!
+ @container.wait_for_running
+ end
+
+ yield new(@container, **wait_args)
+ end
+
+ def self.teardown!
+ @container&.remove!
+ end
+
+ def initialize(container, **wait_args)
+ @container = container
+ wait_for_ready(**wait_args)
+ end
+
+ # @return [String] Base url of mock endpoint
+ def base_url
+ @container.base_url
+ end
+
+ # @return [String] Url of admin endpoint
+ def admin_url
+ @container.admin_url
+ end
+
+ # @param endpoint [String] path for mock endpoint
+ # @return [String] url for mock endpoint
+ def url(endpoint = 'default')
+ "#{base_url}/#{endpoint}"
+ end
+
+ # Waits for the smocker server to be ready
+ #
+ # @param wait [Integer] wait duration for smocker readiness
+ def wait_for_ready(wait: 10)
+ Support::Waiter.wait_until(max_duration: wait, reload_page: false, raise_on_failure: true) do
+ ready?
+ end
+ end
+
+ # Is smocker server ready for interaction?
+ #
+ # @return [Boolean]
+ def ready?
+ QA::Runtime::Logger.debug 'Checking Smocker readiness'
+ get("#{admin_url}/version")
+ true
+ # rescuing StandardError because RestClient::ExceptionWithResponse isn't propagating
+ rescue StandardError => e
+ QA::Runtime::Logger.debug "Smocker not ready yet \n #{e}"
+ false
+ end
+
+ # Clears mocks and history
+ #
+ # @param force [Boolean] remove locked mocks?
+ # @return [Boolean] reset was successful?
+ def reset(force: true)
+ response = post("#{admin_url}/reset?force=#{force}", {}.to_json)
+ parse_body(response)['message'] == 'Reset successful'
+ end
+
+ # Fetches an active session id from a name
+ #
+ # @param name [String] the name of the session
+ # @return [String] the unique session id
+ def get_session_id(name)
+ sessions = parse_body get("#{admin_url}/sessions/summary")
+ current = sessions.find do |session|
+ session[:name] == name
+ end
+ current&.dig(:id)
+ end
+
+ # Registers a mock to Smocker
+ # If a session name is provided, the mock will register to that session
+ # https://smocker.dev/technical-documentation/mock-definition.html
+ #
+ # @param yaml [String] the yaml representing the mock
+ # @param session [String] the session name for the mock
+ def register(yaml = DEFAULT_MOCK, session: nil)
+ query_params = build_params(session: session)
+ url = "#{admin_url}/mocks?#{query_params}"
+ headers = { 'Content-Type' => 'application/x-yaml' }
+ response = post(url, yaml, headers: headers)
+ parse_body(response)
+ end
+
+ # Fetches call history for a mock
+ #
+ # @param session_name [String] the session name for the mock
+ # @return [Array<HistoryResponse>]
+ def history(session_name = nil)
+ query_params = session_name ? build_params(session: get_session_id(session_name)) : ''
+ response = get("#{admin_url}/history?#{query_params}")
+ body = parse_body(response)
+
+ raise body[:message] unless body.is_a?(Array)
+
+ body.map do |entry|
+ HistoryResponse.new(entry)
+ end
+ end
+
+ private
+
+ def build_params(**args)
+ args.each_with_object([]) do |(k, v), memo|
+ memo << "#{k}=#{v}" if v
+ end.join("&")
+ end
+ end
+ end
+ end
+end