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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 14:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 14:10:13 +0300
commit0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch)
tree7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /qa
parent72123183a20411a36d607d70b12d57c484394c8e (diff)
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'qa')
-rw-r--r--qa/.gitignore1
-rw-r--r--qa/Dockerfile14
-rw-r--r--qa/Gemfile4
-rw-r--r--qa/Gemfile.lock51
-rw-r--r--qa/README.md144
-rw-r--r--qa/contracts/.gitignore2
-rw-r--r--qa/contracts/consumer/.node-version1
-rw-r--r--qa/contracts/consumer/endpoints/merge_request.js42
-rw-r--r--qa/contracts/consumer/fixtures/diffs.fixture.js89
-rw-r--r--qa/contracts/consumer/fixtures/discussions.fixture.js85
-rw-r--r--qa/contracts/consumer/fixtures/metadata.fixture.js96
-rw-r--r--qa/contracts/consumer/package.json17
-rw-r--r--qa/contracts/consumer/specs/diffs.spec.js35
-rw-r--r--qa/contracts/consumer/specs/discussions.spec.js35
-rw-r--r--qa/contracts/consumer/specs/metadata.spec.js35
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json228
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json235
-rw-r--r--qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json222
-rw-r--r--qa/contracts/provider/environments/base.rb24
-rw-r--r--qa/contracts/provider/environments/local.rb12
-rw-r--r--qa/contracts/provider/spec/diffs_helper.rb17
-rw-r--r--qa/contracts/provider/spec/discussions_helper.rb17
-rw-r--r--qa/contracts/provider/spec/metadata_helper.rb17
-rw-r--r--qa/contracts/provider/spec_helper.rb22
-rw-r--r--qa/lib/gitlab/page/group/settings/usage_quotas.rb19
-rw-r--r--qa/qa/flow/purchase.rb5
-rw-r--r--qa/qa/mobile/page/profile/menu.rb26
-rw-r--r--qa/qa/mobile/page/sub_menus/common.rb4
-rw-r--r--qa/qa/page/base.rb4
-rw-r--r--qa/qa/page/component/access_tokens.rb14
-rw-r--r--qa/qa/page/component/invite_members_modal.rb2
-rw-r--r--qa/qa/page/component/snippet.rb9
-rw-r--r--qa/qa/page/group/show.rb6
-rw-r--r--qa/qa/page/issuable/new.rb2
-rw-r--r--qa/qa/page/merge_request/show.rb25
-rw-r--r--qa/qa/page/profile/emails.rb9
-rw-r--r--qa/qa/page/profile/menu.rb2
-rw-r--r--qa/qa/page/project/branches/show.rb7
-rw-r--r--qa/qa/page/project/issue/index.rb6
-rw-r--r--qa/qa/page/project/members.rb2
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/pipeline/show.rb35
-rw-r--r--qa/qa/page/project/pipeline_editor/show.rb1
-rw-r--r--qa/qa/page/project/registry/show.rb8
-rw-r--r--qa/qa/page/project/settings/access_tokens.rb1
-rw-r--r--qa/qa/page/project/settings/advanced.rb12
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb4
-rw-r--r--qa/qa/page/project/settings/merge_request.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb2
-rw-r--r--qa/qa/resource/base.rb8
-rw-r--r--qa/qa/resource/members.rb6
-rw-r--r--qa/qa/resource/project.rb14
-rw-r--r--qa/qa/resource/runner.rb8
-rw-r--r--qa/qa/runtime/env.rb8
-rw-r--r--qa/qa/runtime/feature.rb18
-rw-r--r--qa/qa/runtime/logger.rb8
-rw-r--r--qa/qa/runtime/namespace.rb2
-rw-r--r--qa/qa/service/docker_run/base.rb4
-rw-r--r--qa/qa/service/docker_run/gitlab_runner.rb15
-rw-r--r--qa/qa/service/praefect_manager.rb5
-rw-r--r--qa/qa/service/shellout.rb45
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb4
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb16
-rw-r--r--qa/qa/specs/features/api/1_manage/project_access_token_spec.rb4
-rw-r--r--qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb176
-rw-r--r--qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb97
-rw-r--r--qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_non_devops/performance_bar_spec.rb)7
-rw-r--r--qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_default_enabled_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_non_devops/service_ping_default_enabled_spec.rb)7
-rw-r--r--qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_disabled_spec.rb (renamed from qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb)7
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb22
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb109
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/merge_when_pipeline_succeeds_spec.rb78
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb24
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/open_fork_in_web_ide_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb21
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb26
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb113
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb136
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb92
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb1
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb66
-rw-r--r--qa/qa/specs/features/sanity/feature_flags_spec.rb71
-rw-r--r--qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb23
-rw-r--r--qa/qa/specs/helpers/feature_flag.rb10
-rw-r--r--qa/qa/specs/runner.rb3
-rw-r--r--qa/qa/specs/spec_helper.rb14
-rw-r--r--qa/qa/support/api.rb19
-rw-r--r--qa/qa/support/knapsack_report.rb8
-rw-r--r--qa/qa/support/matchers/eventually_matcher.rb2
-rw-r--r--qa/qa/support/page/logging.rb53
-rw-r--r--qa/qa/support/page_error_checker.rb52
-rw-r--r--qa/qa/support/wait_for_requests.rb12
-rw-r--r--qa/qa/tools/delete_projects.rb10
-rw-r--r--qa/qa/tools/delete_subgroups.rb10
-rw-r--r--qa/qa/tools/delete_test_snippets.rb7
-rw-r--r--qa/qa/tools/test_resources_handler.rb39
-rw-r--r--qa/spec/git/repository_spec.rb2
-rw-r--r--qa/spec/resource/base_spec.rb12
-rw-r--r--qa/spec/runtime/env_spec.rb21
-rw-r--r--qa/spec/runtime/feature_spec.rb73
-rw-r--r--qa/spec/runtime/logger_spec.rb2
-rw-r--r--qa/spec/service/shellout_spec.rb58
-rw-r--r--qa/spec/specs/helpers/feature_flag_spec.rb22
-rw-r--r--qa/spec/specs/runner_spec.rb52
-rw-r--r--qa/spec/support/page_error_checker_spec.rb68
-rw-r--r--qa/spec/support/wait_for_requests_spec.rb16
-rw-r--r--qa/tasks/contracts.rake47
-rw-r--r--qa/tasks/knapsack.rake12
-rw-r--r--qa/tmp/.gitignore3
127 files changed, 1355 insertions, 2255 deletions
diff --git a/qa/.gitignore b/qa/.gitignore
index 3c5db4b565e..31ab17eeeaf 100644
--- a/qa/.gitignore
+++ b/qa/.gitignore
@@ -1,4 +1,3 @@
-tmp/
reports/
no_of_examples/
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 4fd44ba02df..5d046636984 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -1,8 +1,12 @@
-FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-git-2.33-lfs-2.9-chrome-99-docker-20.10.14-gcloud-383-kubectl-1.23
+ARG DOCKER_VERSION=20.10.14
+ARG CHROME_VERSION=101
+
+FROM registry.gitlab.com/gitlab-org/gitlab-build-images/debian-bullseye-ruby-2.7:bundler-2.3-git-2.33-lfs-2.9-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION}-gcloud-383-kubectl-1.23
LABEL maintainer="GitLab Quality Department <quality@gitlab.com>"
-ENV DEBIAN_FRONTEND="noninteractive" \
- BUNDLE_WITHOUT=development
+ENV DEBIAN_FRONTEND="noninteractive"
+# Override config path to make sure local config doesn't override it when building image locally
+ENV BUNDLE_APP_CONFIG=/home/gitlab/.bundle
##
# Install system libs
@@ -27,7 +31,8 @@ WORKDIR /home/gitlab/qa
# Install qa dependencies or fetch from cache if unchanged
#
COPY ./qa/Gemfile* /home/gitlab/qa/
-RUN bundle install --jobs=$(nproc) --retry=3
+RUN bundle config set --local without development \
+ && bundle install --retry=3
##
# Fetch chromedriver based on version of chrome
@@ -42,6 +47,7 @@ COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/co
# The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS)
COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/
COPY ./config/bundler_setup.rb /home/gitlab/config/
+COPY ./config/feature_flags /home/gitlab/config/feature_flags
COPY ./lib/gitlab_edition.rb /home/gitlab/lib/
COPY ./lib/gitlab/utils.rb /home/gitlab/lib/gitlab/
COPY ./INSTALLATION_TYPE ./VERSION /home/gitlab/
diff --git a/qa/Gemfile b/qa/Gemfile
index b504d6d4e90..d8d00400563 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 7', require: 'gitlab/qa'
gem 'activesupport', '~> 6.1.4.7' # This should stay in sync with the root's Gemfile
gem 'allure-rspec', '~> 2.16.0'
gem 'capybara', '~> 3.35.0'
@@ -35,8 +35,6 @@ gem 'confiner', '~> 0.3'
gem 'chemlab', '~> 0.9'
gem 'chemlab-library-www-gitlab-com', '~> 0.1'
-gem "pact", "~> 1.12"
-
gem 'deprecation_toolkit', '~> 1.5.1', require: false
group :development do
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index c4809a17f66..71484c30c9a 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -28,7 +28,6 @@ GEM
require_all (>= 2, < 4)
uuid (>= 2.3, < 3)
ast (2.4.2)
- awesome_print (1.9.2)
binding_ninja (0.2.3)
builder (3.2.4)
byebug (9.1.0)
@@ -92,8 +91,6 @@ GEM
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
- filelock (1.1.1)
- find_a_port (1.0.1)
fog-core (2.1.0)
builder
excon (~> 0.58)
@@ -121,7 +118,7 @@ GEM
gitlab (4.18.0)
httparty (~> 0.18)
terminal-table (>= 1.5.1)
- gitlab-qa (7.24.4)
+ gitlab-qa (7.33.0)
activesupport (~> 6.1)
gitlab (~> 4.18.0)
http (~> 5.0)
@@ -166,7 +163,7 @@ GEM
http-form_data (~> 2.2)
llhttp-ffi (~> 0.4.0)
http-accept (1.7.0)
- http-cookie (1.0.4)
+ http-cookie (1.0.5)
domain_name (~> 0.5)
http-form_data (2.3.0)
httparty (0.20.0)
@@ -177,7 +174,6 @@ GEM
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb-client (1.17.0)
- json (2.6.1)
jwt (2.3.0)
knapsack (4.0.0)
rake
@@ -202,7 +198,7 @@ GEM
multi_xml (0.6.0)
multipart-post (2.1.1)
netrc (0.11.0)
- nokogiri (1.13.3)
+ nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
octokit (4.21.0)
@@ -210,29 +206,6 @@ GEM
sawyer (~> 0.8.0, >= 0.5.3)
oj (3.13.11)
os (1.1.4)
- pact (1.59.0)
- pact-mock_service (~> 3.0, >= 3.3.1)
- pact-support (~> 1.15)
- rack-test (>= 0.6.3, < 2.0.0)
- rspec (~> 3.0)
- term-ansicolor (~> 1.0)
- thor (>= 0.20, < 2.0)
- webrick (~> 1.3)
- pact-mock_service (3.6.2)
- filelock (~> 1.1)
- find_a_port (~> 1.0.1)
- json
- pact-support (~> 1.12, >= 1.12.0)
- rack (~> 2.0)
- rspec (>= 2.14)
- term-ansicolor (~> 1.0)
- thor (>= 0.19, < 2.0)
- webrick (~> 1.3)
- pact-support (1.15.1)
- awesome_print (~> 1.1)
- randexp (~> 0.1.7)
- rspec (>= 2.14)
- term-ansicolor (~> 1.0)
parallel (1.19.2)
parallel_tests (2.29.0)
parallel
@@ -249,14 +222,13 @@ GEM
pry-byebug (3.5.1)
byebug (~> 9.1)
pry (~> 0.10)
- public_suffix (4.0.6)
+ public_suffix (4.0.7)
racc (1.6.0)
- rack (2.2.3)
+ rack (2.2.3.1)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rainbow (3.0.0)
rake (13.0.6)
- randexp (0.1.7)
regexp_parser (2.1.1)
representable (3.1.1)
declarative (< 0.1.0)
@@ -311,25 +283,19 @@ GEM
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
slack-notifier (2.4.0)
- sync (0.5.0)
systemu (2.6.5)
table_print (1.5.7)
- term-ansicolor (1.7.1)
- tins (~> 1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
- thor (1.2.1)
thread_safe (0.3.6)
timecop (0.9.1)
- tins (1.31.0)
- sync
trailblazer-option (0.1.2)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.8.1)
+ unf_ext (0.0.8.2)
unicode-display_width (2.1.0)
unparser (0.4.7)
abstract_type (~> 0.0.7)
@@ -368,11 +334,10 @@ DEPENDENCIES
deprecation_toolkit (~> 1.5.1)
faker (~> 2.19, >= 2.19.0)
fog-google (~> 1.17)
- gitlab-qa
+ gitlab-qa (~> 7)
influxdb-client (~> 1.17)
knapsack (~> 4.0)
octokit (~> 4.21)
- pact (~> 1.12)
parallel (~> 1.19)
parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
@@ -393,4 +358,4 @@ DEPENDENCIES
zeitwerk (~> 2.4)
BUNDLED WITH
- 2.3.6
+ 2.3.15
diff --git a/qa/README.md b/qa/README.md
index 724638d13c0..dbc70f55f1c 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -39,78 +39,73 @@ have an instance available you can follow the instructions below to use
the [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit).
This is the recommended option if you would like to contribute to the tests.
-Note: GitLab QA uses [Selenium WebDriver](https://www.seleniumhq.org/) via
-[Cabybara](http://teamcapybara.github.io/capybara/), and by default it targets Chrome as
-the browser to use. You will need to have Chrome (or Chromium) and
-[chromedriver](https://chromedriver.chromium.org/) installed / in your `$PATH`.
+Note that tests are using `Chrome` web browser by default so it should be installed and present in `PATH`.
+
+## CI
+
+Tests are executed in merge request pipelines as part of the development lifecycle.
+
+- [Review app environment](../doc/development/testing_guide/review_apps.md)
+- [package-and-qa](../doc/development/testing_guide/end_to_end/index.md#testing-code-in-merge-requests)
+
+### Logging
+
+By default tests on CI use `info` log level. `debug` level is still available in case of failure debugging. Logs are stored in jobs artifacts.
### Writing tests
- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/beginners_guide.md)
- - [Best practices](../doc/development/testing_guide/best_practices.md)
- - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
- - [Guidelines](../doc/development/testing_guide/index.md)
- - [Tests with special setup for local environments](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
+ - [Best practices](../doc/development/testing_guide/best_practices.md)
+ - [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
+ - [Guidelines](../doc/development/testing_guide/index.md)
+ - [Tests with special setup for local environments](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
### Run the end-to-end tests in a local development environment
-Follow the GDK instructions to [install](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/index.md) your local GitLab development environment.
-
-Once you have GDK running, switch to the `qa` directory. E.g., if you setup
-GDK to develop in the main `gitlab-ce` repo, the GitLab source code will be
-in a `gitlab` directory and so the end-to-end test code will be in `gitlab/qa`.
+1. Follow the instructions to [install GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/index.md), your local GitLab development environment.
-From there you can run the tests. For example, the
-following call would login to the GDK instance and run all specs in
-`qa/specs/features`:
+1. Navigate to the QA folder and run the following commands.
+```bash
+cd gitlab-development-kit/gitlab/qa
+bundle install
+export WEBDRIVER_HEADLESS=false
+export GITLAB_INITIAL_ROOT_PASSWORD={your current root user's password}
```
-# Make sure to install the dependencies first with `bundle install`
-bundle exec bin/qa Test::Instance::All http://localhost:3000
-```
+1. Most tests that do not require special setup could simply be run with the following command. However, tests that are tagged with `:orchestrated` tag require special setup. These tests can only be run with [bin/qa](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/README.md#running-tests-with-a-custom-binqa-test-runner) script.
-Note: If you want to run tests requiring SSH against GDK, you
-will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
+```bash
+bundle exec rspec <path/to/spec.rb>
+```
-Note: When you log into your GDK instance of GitLab for the first time, the root password requires a change.
-GitLab QA expects the default initial password to be used in tests; see all default values listed in
-[Supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
-If you have changed your root password, you must set the `GITLAB_INITIAL_ROOT_PASSWORD` environment
-variable.
+1. For test that are tagged with `:orchestrated`, [re-configure IP address in GDK](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/run_qa_against_gdk.md#run-qa-tests-against-your-gdk-setup) to run QA tests. Once you have reconfigured GDK, ensure GitLab is running successfully on the IP address configured, then run the following command:
-```
-export GITLAB_INITIAL_ROOT_PASSWORD="<GDK root password>"
+```bash
+bundle exec bin/qa Test::Instance::All {GDK IP ADDRESS}
```
+- Note: If you want to run tests requiring SSH against GDK, you will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
+- Note: If this is your first time running GDK, you can use the password pre-set for `root`. [See supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables). If you have changed your `root` password, use that when exporting `GITLAB_INITIAL_ROOT_PASSWORD`.
+
#### Running EE tests
When running EE tests you'll need to have a license available. GitLab engineers can [request a license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee).
Once you have the license file you can export it as an environment variable and then the framework can use it. If you do so it will be installed automatically.
-```
+```shell
export EE_LICENSE=$(cat /path/to/gitlab_license)
```
-### Running specific tests
+#### Running specific tests
You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
+```shell
+bundle exec rspec qa/specs/features/browser_ui/3_create/repository
```
-bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/repository
-```
-
-Since the arguments would be passed to `rspec`, you could use all `rspec`
-options there. For example, passing `--backtrace` and also line number:
-
-```
-bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace
-```
-
-Note that the separator `--` is required; all subsequent options will be
-ignored by the QA framework and passed to `rspec`.
#### Running tests for transient bugs
@@ -118,10 +113,18 @@ A suite of tests have been written to test for [transient bugs](https://about.gi
Those tests are tagged `:transient` and therefore can be run via:
```shell
-bundle exec bin/qa Test::Instance::All http://localhost:3000 -- --tag transient
+bundle exec rspec --tag transient
```
-### Overriding the authenticated user
+#### Overriding gitlab address
+
+When running tests against GDK, the default address is `http://127.0.0.1:3000`. This value can be overridden by providing environment variable `QA_GITLAB_URL`:
+
+```shell
+QA_GITLAB_URL=https://gdk.test:3000 bundle exec rspec
+```
+
+#### Overriding the authenticated user
Unless told otherwise, the QA tests will run as the default `root` user seeded
by the GDK.
@@ -129,8 +132,8 @@ by the GDK.
If you need to authenticate as a different user, you can provide the
`GITLAB_USERNAME` and `GITLAB_PASSWORD` environment variables:
-```
-GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
+```shell
+GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec rspec
```
Some QA tests require logging in as an admin user. By default, the QA
@@ -140,21 +143,21 @@ If you need to authenticate with different admin credentials, you can
provide the `GITLAB_ADMIN_USERNAME` and `GITLAB_ADMIN_PASSWORD`
environment variables:
-```
-GITLAB_ADMIN_USERNAME=admin GITLAB_ADMIN_PASSWORD=myadminpassword GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
+```shell
+GITLAB_ADMIN_USERNAME=admin GITLAB_ADMIN_PASSWORD=myadminpassword GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec rspec
```
If your user doesn't have permission to default sandbox group
`gitlab-qa-sandbox`, you could also use another sandbox group by giving
`GITLAB_SANDBOX_NAME`:
-```
-GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bundle exec bin/qa Test::Instance::All https://gitlab.example.com
+```shell
+GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bundle exec rspec
```
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
-### Sending additional cookies
+#### Sending additional cookies
The environment variable `QA_COOKIES` can be set to send additional cookies
on every request. This is necessary on gitlab.com to direct traffic to the
@@ -162,6 +165,21 @@ canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`.
To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
+#### Headless browser
+
+By default tests use headless browser. To override that, `WEBDRIVER_HEADLESS` must be set to `false`:
+
+```shell
+WEBDRIVER_HEADLESS=false bundle exec rspec
+```
+
+#### Log level
+
+By default, the tests use the `info` log level. To change the test's log level, the environment variable `QA_LOG_LEVEL` can be set:
+
+```shell
+QA_LOG_LEVEL=debug bundle exec rspec
+```
### Building a Docker image to test
@@ -182,20 +200,19 @@ tests that are expected to fail while a fix is in progress (similar to how
[`skip` or `pending`](https://relishapp.com/rspec/rspec-core/v/3-8/docs/pending-and-skipped-examples)
can be used).
-```
-bundle exec bin/qa Test::Instance::All http://localhost:3000 -- --tag quarantine
+```shell
+bundle exec rspec --tag quarantine
```
-If `quarantine` is used with other tags, tests will only be run if they have at
-least one of the tags other than `quarantine`. This is different from how RSpec
-tags usually work, where all tags are inclusive.
+### Running tests with a custom bin/qa test runner
-For example, suppose one test has `:smoke` and `:quarantine` metadata, and
-another test has `:ldap` and `:quarantine` metadata. If the tests are run with
-`--tag smoke --tag quarantine`, only the first test will run. The test with
-`:ldap` will not run even though it also has `:quarantine`.
+`bin/qa` is an additional custom wrapper script that abstracts away some of the more complicated setups that some tests require. This option requires test scenario and test instance's Gitlab address to be specified in the command. For example, to run any `Instance` scenario test, the following command can be used:
+
+```shell
+bundle exec bin/qa Test::Instance::All http://localhost:3000
+```
-### Running tests with a feature flag enabled or disabled
+#### Running tests with a feature flag enabled or disabled
Tests can be run with a feature flag enabled or disabled by using the command-line
option `--enable-feature FEATURE_FLAG` or `--disable-feature FEATURE_FLAG`.
@@ -203,7 +220,7 @@ option `--enable-feature FEATURE_FLAG` or `--disable-feature FEATURE_FLAG`.
For example, to enable the feature flag that enforces Gitaly request limits,
you would use the command:
-```
+```shell
bundle exec bin/qa Test::Instance::All http://localhost:3000 --enable-feature gitaly_enforce_requests_limits
```
@@ -215,9 +232,10 @@ feature flag again.
Similarly, to disable the feature flag that enforces Gitaly request limits,
you would use the command:
-```
+```shell
bundle exec bin/qa Test::Instance::All http://localhost:3000 --disable-feature gitaly_enforce_requests_limits
```
+
This will instruct the QA framework to disable the `gitaly_enforce_requests_limits`
feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)) if not already disabled,
run all the tests in the `Test::Instance::All` scenario, and then enable the
diff --git a/qa/contracts/.gitignore b/qa/contracts/.gitignore
deleted file mode 100644
index cb89d4102d3..00000000000
--- a/qa/contracts/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-logs/
-consumer/node_modules
diff --git a/qa/contracts/consumer/.node-version b/qa/contracts/consumer/.node-version
deleted file mode 100644
index 18711d290ea..00000000000
--- a/qa/contracts/consumer/.node-version
+++ /dev/null
@@ -1 +0,0 @@
-14.17.5
diff --git a/qa/contracts/consumer/endpoints/merge_request.js b/qa/contracts/consumer/endpoints/merge_request.js
deleted file mode 100644
index 74fd4e75bec..00000000000
--- a/qa/contracts/consumer/endpoints/merge_request.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict';
-
-const axios = require('axios');
-
-exports.getMetadata = (endpoint) => {
- const url = endpoint.url;
-
- return axios
- .request({
- method: 'GET',
- baseURL: url,
- url: '/diffs_metadata.json',
- headers: { Accept: '*/*' },
- })
- .then((response) => response.data);
-};
-
-exports.getDiscussions = (endpoint) => {
- const url = endpoint.url;
-
- return axios
- .request({
- method: 'GET',
- baseURL: url,
- url: '/discussions.json',
- headers: { Accept: '*/*' },
- })
- .then((response) => response.data);
-};
-
-exports.getDiffs = (endpoint) => {
- const url = endpoint.url;
-
- return axios
- .request({
- method: 'GET',
- baseURL: url,
- url: '/diffs_batch.json?page=0',
- headers: { Accept: '*/*' },
- })
- .then((response) => response.data);
-};
diff --git a/qa/contracts/consumer/fixtures/diffs.fixture.js b/qa/contracts/consumer/fixtures/diffs.fixture.js
deleted file mode 100644
index 286d71f421c..00000000000
--- a/qa/contracts/consumer/fixtures/diffs.fixture.js
+++ /dev/null
@@ -1,89 +0,0 @@
-'use strict';
-
-const { Matchers } = require('@pact-foundation/pact');
-
-const body = {
- diff_files: Matchers.eachLike({
- content_sha: Matchers.string('b0c94059db75b2473d616d4b1fde1a77533355a3'),
- submodule: Matchers.boolean(false),
- edit_path: Matchers.string('/gitlab-qa-bot/...'),
- ide_edit_path: Matchers.string('/gitlab-qa-bot/...'),
- old_path_html: Matchers.string('Gemfile'),
- new_path_html: Matchers.string('Gemfile'),
- blob: {
- id: Matchers.string('855071bb3928d140764885964f7be1bb3e582495'),
- path: Matchers.string('Gemfile'),
- name: Matchers.string('Gemfile'),
- mode: Matchers.string('1234567'),
- readable_text: Matchers.boolean(true),
- icon: Matchers.string('doc-text'),
- },
- can_modify_blob: Matchers.boolean(false),
- file_identifier_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- file_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- file_path: Matchers.string('Gemfile'),
- old_path: Matchers.string('Gemfile'),
- new_path: Matchers.string('Gemfile'),
- new_file: Matchers.boolean(false),
- renamed_file: Matchers.boolean(false),
- deleted_file: Matchers.boolean(false),
- diff_refs: {
- base_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- start_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- head_sha: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- },
- mode_changed: Matchers.boolean(false),
- a_mode: Matchers.string('123456'),
- b_mode: Matchers.string('123456'),
- viewer: {
- name: Matchers.string('text'),
- collapsed: Matchers.boolean(false),
- },
- old_size: Matchers.integer(2288),
- new_size: Matchers.integer(2288),
- added_lines: Matchers.integer(1),
- removed_lines: Matchers.integer(1),
- load_collapsed_diff_url: Matchers.string('/gitlab-qa-bot/...'),
- view_path: Matchers.string('/gitlab-qa-bot/...'),
- context_lines_path: Matchers.string('/gitlab-qa-bot/...'),
- highlighted_diff_lines: Matchers.eachLike({
- // The following values can also be null which is not supported
- //line_code: Matchers.string('de3150c01c3a946a6168173c4116741379fe3579_1_1'),
- //old_line: Matchers.integer(1),
- //new_line: Matchers.integer(1),
- text: Matchers.string('source'),
- rich_text: Matchers.string('<span></span>'),
- can_receive_suggestion: Matchers.boolean(true),
- }),
- is_fully_expanded: Matchers.boolean(false),
- }),
- pagination: {
- total_pages: Matchers.integer(1),
- },
-};
-
-const Diffs = {
- body: Matchers.extractPayload(body),
-
- success: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- body: body,
- },
-
- request: {
- uponReceiving: 'a request for diff lines',
- withRequest: {
- method: 'GET',
- path: '/diffs_batch.json',
- headers: {
- Accept: '*/*',
- },
- query: 'page=0',
- },
- },
-};
-
-exports.Diffs = Diffs;
diff --git a/qa/contracts/consumer/fixtures/discussions.fixture.js b/qa/contracts/consumer/fixtures/discussions.fixture.js
deleted file mode 100644
index cfc6112561b..00000000000
--- a/qa/contracts/consumer/fixtures/discussions.fixture.js
+++ /dev/null
@@ -1,85 +0,0 @@
-'use strict';
-
-const { Matchers } = require('@pact-foundation/pact');
-
-const body = Matchers.eachLike({
- id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
- reply_id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
- project_id: Matchers.integer(6954442),
- confidential: Matchers.boolean(false),
- diff_discussion: Matchers.boolean(false),
- expanded: Matchers.boolean(false),
- for_commit: Matchers.boolean(false),
- individual_note: Matchers.boolean(true),
- resolvable: Matchers.boolean(false),
- resolved_by_push: Matchers.boolean(false),
- notes: Matchers.eachLike({
- id: Matchers.string('76489845'),
- author: {
- id: Matchers.integer(1675733),
- username: Matchers.string('gitlab-qa-bot'),
- name: Matchers.string('gitlab-qa-bot'),
- state: Matchers.string('active'),
- avatar_url: Matchers.string(
- 'https://secure.gravatar.com/avatar/8355ad0f2761367fae6b9c4fe80994b9?s=80&d=identicon',
- ),
- show_status: Matchers.boolean(false),
- path: Matchers.string('/gitlab-qa-bot'),
- },
- created_at: Matchers.iso8601DateTimeWithMillis('2022-02-22T07:06:55.038Z'),
- updated_at: Matchers.iso8601DateTimeWithMillis('2022-02-22T07:06:55.038Z'),
- system: Matchers.boolean(false),
- noteable_id: Matchers.integer(8333422),
- noteable_type: Matchers.string('MergeRequest'),
- resolvable: Matchers.boolean(false),
- resolved: Matchers.boolean(true),
- confidential: Matchers.boolean(false),
- noteable_iid: Matchers.integer(1),
- note: Matchers.string('This is a test comment'),
- note_html: Matchers.string(
- '<p data-sourcepos="1:1-1:22" dir="auto">This is a test comment</p>',
- ),
- current_user: {
- can_edit: Matchers.boolean(true),
- can_award_emoji: Matchers.boolean(true),
- can_resolve: Matchers.boolean(false),
- can_resolve_discussion: Matchers.boolean(false),
- },
- is_noteable_author: Matchers.boolean(true),
- discussion_id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
- emoji_awardable: Matchers.boolean(true),
- report_abuse_path: Matchers.string('/gitlab-qa-bot/...'),
- noteable_note_url: Matchers.string('https://staging.gitlab.com/gitlab-qa-bot/...'),
- cached_markdown_version: Matchers.integer(1900552),
- human_access: Matchers.string('Maintainer'),
- is_contributor: Matchers.boolean(false),
- project_name: Matchers.string('contract-testing'),
- path: Matchers.string('/gitlab-qa-bot/...'),
- }),
- resolved: Matchers.boolean(true),
-});
-
-const Discussions = {
- body: Matchers.extractPayload(body),
-
- success: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- body: body,
- },
-
- request: {
- uponReceiving: 'a request for discussions',
- withRequest: {
- method: 'GET',
- path: '/discussions.json',
- headers: {
- Accept: '*/*',
- },
- },
- },
-};
-
-exports.Discussions = Discussions;
diff --git a/qa/contracts/consumer/fixtures/metadata.fixture.js b/qa/contracts/consumer/fixtures/metadata.fixture.js
deleted file mode 100644
index 05a4831c447..00000000000
--- a/qa/contracts/consumer/fixtures/metadata.fixture.js
+++ /dev/null
@@ -1,96 +0,0 @@
-'use strict';
-
-const { Matchers } = require('@pact-foundation/pact');
-
-const body = {
- real_size: Matchers.string('1'),
- size: Matchers.integer(1),
- branch_name: Matchers.string('testing-branch-1'),
- source_branch_exists: Matchers.boolean(true),
- target_branch_name: Matchers.string('master'),
- merge_request_diff: {
- created_at: Matchers.iso8601DateTimeWithMillis('2022-02-17T11:47:08.804Z'),
- commits_count: Matchers.integer(1),
- latest: Matchers.boolean(true),
- short_commit_sha: Matchers.string('aee1ffec'),
- base_version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
- ),
- head_version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true',
- ),
- version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
- ),
- compare_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f',
- ),
- },
- latest_diff: Matchers.boolean(true),
- latest_version_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs'),
- added_lines: Matchers.integer(1),
- removed_lines: Matchers.integer(1),
- render_overflow_warning: Matchers.boolean(false),
- email_patch_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1.patch'),
- plain_diff_path: Matchers.string('/gitlab-qa-bot/contract-testing/-/merge_requests/1.diff'),
- merge_request_diffs: Matchers.eachLike({
- commits_count: Matchers.integer(1),
- latest: Matchers.boolean(true),
- short_commit_sha: Matchers.string('aee1ffec'),
- base_version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
- ),
- head_version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true',
- ),
- version_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773',
- ),
- compare_path: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f',
- ),
- }),
- definition_path_prefix: Matchers.string(
- '/gitlab-qa-bot/contract-testing/-/blob/aee1ffec2299c0cfb17c8821e931339b73a3759f',
- ),
- diff_files: Matchers.eachLike({
- added_lines: Matchers.integer(1),
- removed_lines: Matchers.integer(1),
- new_path: Matchers.string('Gemfile'),
- old_path: Matchers.string('Gemfile'),
- new_file: Matchers.boolean(false),
- deleted_file: Matchers.boolean(false),
- submodule: Matchers.boolean(false),
- file_identifier_hash: Matchers.string('67d82b8716a5b6c52c7abf0b2cd99c7594ed3587'),
- file_hash: Matchers.string('de3150c01c3a946a6168173c4116741379fe3579'),
- }),
- has_conflicts: Matchers.boolean(false),
- can_merge: Matchers.boolean(false),
- project_path: Matchers.string('gitlab-qa-bot/contract-testing'),
- project_name: Matchers.string('contract-testing'),
-};
-
-const Metadata = {
- body: Matchers.extractPayload(body),
-
- success: {
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf-8',
- },
- body: body,
- },
-
- request: {
- uponReceiving: 'a request for Metadata',
- withRequest: {
- method: 'GET',
- path: '/diffs_metadata.json',
- headers: {
- Accept: '*/*',
- },
- },
- },
-};
-
-exports.Metadata = Metadata;
diff --git a/qa/contracts/consumer/package.json b/qa/contracts/consumer/package.json
deleted file mode 100644
index b4a3f59e89e..00000000000
--- a/qa/contracts/consumer/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "name": "consumer",
- "version": "1.0.0",
- "description": "consumer side contract testing",
- "license": "MIT",
- "repository": "https://gitlab.com/gitlab-org/gitlab.git",
- "dependencies": {
- "@pact-foundation/pact": "^9.17.2",
- "axios": "^0.26.0",
- "jest": "^27.5.1",
- "jest-pact": "^0.9.1",
- "prettier": "^2.5.1"
- },
- "scripts": {
- "test": "jest specs/ --runInBand"
- }
-}
diff --git a/qa/contracts/consumer/specs/diffs.spec.js b/qa/contracts/consumer/specs/diffs.spec.js
deleted file mode 100644
index 5be2ed7ac00..00000000000
--- a/qa/contracts/consumer/specs/diffs.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-const { pactWith } = require('jest-pact');
-
-const { Diffs } = require('../fixtures/diffs.fixture');
-const { getDiffs } = require('../endpoints/merge_request');
-
-pactWith(
- {
- consumer: 'Merge Request Page',
- provider: 'Merge Request Diffs Endpoint',
- log: '../logs/consumer.log',
- dir: '../contracts',
- },
-
- (provider) => {
- describe('Diffs Endpoint', () => {
- beforeEach(() => {
- const interaction = {
- ...Diffs.request,
- willRespondWith: Diffs.success,
- };
- return provider.addInteraction(interaction);
- });
-
- it('return a successful body', () => {
- return getDiffs({
- url: provider.mockService.baseUrl,
- }).then((diffs) => {
- expect(diffs).toEqual(Diffs.body);
- });
- });
- });
- },
-);
diff --git a/qa/contracts/consumer/specs/discussions.spec.js b/qa/contracts/consumer/specs/discussions.spec.js
deleted file mode 100644
index 28ee3186a9f..00000000000
--- a/qa/contracts/consumer/specs/discussions.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-const { pactWith } = require('jest-pact');
-
-const { Discussions } = require('../fixtures/discussions.fixture');
-const { getDiscussions } = require('../endpoints/merge_request');
-
-pactWith(
- {
- consumer: 'Merge Request Page',
- provider: 'Merge Request Discussions Endpoint',
- log: '../logs/consumer.log',
- dir: '../contracts',
- },
-
- (provider) => {
- describe('Discussions Endpoint', () => {
- beforeEach(() => {
- const interaction = {
- ...Discussions.request,
- willRespondWith: Discussions.success,
- };
- return provider.addInteraction(interaction);
- });
-
- it('return a successful body', () => {
- return getDiscussions({
- url: provider.mockService.baseUrl,
- }).then((discussions) => {
- expect(discussions).toEqual(Discussions.body);
- });
- });
- });
- },
-);
diff --git a/qa/contracts/consumer/specs/metadata.spec.js b/qa/contracts/consumer/specs/metadata.spec.js
deleted file mode 100644
index 31fc398f228..00000000000
--- a/qa/contracts/consumer/specs/metadata.spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-const { pactWith } = require('jest-pact');
-
-const { Metadata } = require('../fixtures/metadata.fixture');
-const { getMetadata } = require('../endpoints/merge_request');
-
-pactWith(
- {
- consumer: 'Merge Request Page',
- provider: 'Merge Request Metadata Endpoint',
- log: '../logs/consumer.log',
- dir: '../contracts',
- },
-
- (provider) => {
- describe('Metadata Endpoint', () => {
- beforeEach(() => {
- const interaction = {
- ...Metadata.request,
- willRespondWith: Metadata.success,
- };
- return provider.addInteraction(interaction);
- });
-
- it('return a successful body', () => {
- return getMetadata({
- url: provider.mockService.baseUrl,
- }).then((metadata) => {
- expect(metadata).toEqual(Metadata.body);
- });
- });
- });
- },
-);
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json
deleted file mode 100644
index 8df54c25326..00000000000
--- a/qa/contracts/contracts/merge_request_page-merge_request_diffs_endpoint.json
+++ /dev/null
@@ -1,228 +0,0 @@
-{
- "consumer": {
- "name": "Merge Request Page"
- },
- "provider": {
- "name": "Merge Request Diffs Endpoint"
- },
- "interactions": [
- {
- "description": "a request for diff lines",
- "request": {
- "method": "GET",
- "path": "/diffs_batch.json",
- "query": "page=0",
- "headers": {
- "Accept": "*/*"
- }
- },
- "response": {
- "status": 200,
- "headers": {
- "Content-Type": "application/json; charset=utf-8"
- },
- "body": {
- "diff_files": [
- {
- "content_sha": "b0c94059db75b2473d616d4b1fde1a77533355a3",
- "submodule": false,
- "edit_path": "/gitlab-qa-bot/...",
- "ide_edit_path": "/gitlab-qa-bot/...",
- "old_path_html": "Gemfile",
- "new_path_html": "Gemfile",
- "blob": {
- "id": "855071bb3928d140764885964f7be1bb3e582495",
- "path": "Gemfile",
- "name": "Gemfile",
- "mode": "1234567",
- "readable_text": true,
- "icon": "doc-text"
- },
- "can_modify_blob": false,
- "file_identifier_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
- "file_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
- "file_path": "Gemfile",
- "old_path": "Gemfile",
- "new_path": "Gemfile",
- "new_file": false,
- "renamed_file": false,
- "deleted_file": false,
- "diff_refs": {
- "base_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
- "start_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
- "head_sha": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587"
- },
- "mode_changed": false,
- "a_mode": "123456",
- "b_mode": "123456",
- "viewer": {
- "name": "text",
- "collapsed": false
- },
- "old_size": 2288,
- "new_size": 2288,
- "added_lines": 1,
- "removed_lines": 1,
- "load_collapsed_diff_url": "/gitlab-qa-bot/...",
- "view_path": "/gitlab-qa-bot/...",
- "context_lines_path": "/gitlab-qa-bot/...",
- "highlighted_diff_lines": [
- {
- "text": "source",
- "rich_text": "<span></span>",
- "can_receive_suggestion": true
- }
- ],
- "is_fully_expanded": false
- }
- ],
- "pagination": {
- "total_pages": 1
- }
- },
- "matchingRules": {
- "$.body.diff_files": {
- "min": 1
- },
- "$.body.diff_files[*].*": {
- "match": "type"
- },
- "$.body.diff_files[*].content_sha": {
- "match": "type"
- },
- "$.body.diff_files[*].submodule": {
- "match": "type"
- },
- "$.body.diff_files[*].edit_path": {
- "match": "type"
- },
- "$.body.diff_files[*].ide_edit_path": {
- "match": "type"
- },
- "$.body.diff_files[*].old_path_html": {
- "match": "type"
- },
- "$.body.diff_files[*].new_path_html": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.id": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.path": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.name": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.mode": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.readable_text": {
- "match": "type"
- },
- "$.body.diff_files[*].blob.icon": {
- "match": "type"
- },
- "$.body.diff_files[*].can_modify_blob": {
- "match": "type"
- },
- "$.body.diff_files[*].file_identifier_hash": {
- "match": "type"
- },
- "$.body.diff_files[*].file_hash": {
- "match": "type"
- },
- "$.body.diff_files[*].file_path": {
- "match": "type"
- },
- "$.body.diff_files[*].old_path": {
- "match": "type"
- },
- "$.body.diff_files[*].new_path": {
- "match": "type"
- },
- "$.body.diff_files[*].new_file": {
- "match": "type"
- },
- "$.body.diff_files[*].renamed_file": {
- "match": "type"
- },
- "$.body.diff_files[*].deleted_file": {
- "match": "type"
- },
- "$.body.diff_files[*].diff_refs.base_sha": {
- "match": "type"
- },
- "$.body.diff_files[*].diff_refs.start_sha": {
- "match": "type"
- },
- "$.body.diff_files[*].diff_refs.head_sha": {
- "match": "type"
- },
- "$.body.diff_files[*].mode_changed": {
- "match": "type"
- },
- "$.body.diff_files[*].a_mode": {
- "match": "type"
- },
- "$.body.diff_files[*].b_mode": {
- "match": "type"
- },
- "$.body.diff_files[*].viewer.name": {
- "match": "type"
- },
- "$.body.diff_files[*].viewer.collapsed": {
- "match": "type"
- },
- "$.body.diff_files[*].old_size": {
- "match": "type"
- },
- "$.body.diff_files[*].new_size": {
- "match": "type"
- },
- "$.body.diff_files[*].added_lines": {
- "match": "type"
- },
- "$.body.diff_files[*].removed_lines": {
- "match": "type"
- },
- "$.body.diff_files[*].load_collapsed_diff_url": {
- "match": "type"
- },
- "$.body.diff_files[*].view_path": {
- "match": "type"
- },
- "$.body.diff_files[*].context_lines_path": {
- "match": "type"
- },
- "$.body.diff_files[*].highlighted_diff_lines": {
- "min": 1
- },
- "$.body.diff_files[*].highlighted_diff_lines[*].*": {
- "match": "type"
- },
- "$.body.diff_files[*].highlighted_diff_lines[*].text": {
- "match": "type"
- },
- "$.body.diff_files[*].highlighted_diff_lines[*].rich_text": {
- "match": "type"
- },
- "$.body.diff_files[*].highlighted_diff_lines[*].can_receive_suggestion": {
- "match": "type"
- },
- "$.body.diff_files[*].is_fully_expanded": {
- "match": "type"
- },
- "$.body.pagination.total_pages": {
- "match": "type"
- }
- }
- }
- }
- ],
- "metadata": {
- "pactSpecification": {
- "version": "2.0.0"
- }
- }
-} \ No newline at end of file
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json
deleted file mode 100644
index 14839053e57..00000000000
--- a/qa/contracts/contracts/merge_request_page-merge_request_discussions_endpoint.json
+++ /dev/null
@@ -1,235 +0,0 @@
-{
- "consumer": {
- "name": "Merge Request Page"
- },
- "provider": {
- "name": "Merge Request Discussions Endpoint"
- },
- "interactions": [
- {
- "description": "a request for discussions",
- "request": {
- "method": "GET",
- "path": "/discussions.json",
- "headers": {
- "Accept": "*/*"
- }
- },
- "response": {
- "status": 200,
- "headers": {
- "Content-Type": "application/json; charset=utf-8"
- },
- "body": [
- {
- "id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
- "reply_id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
- "project_id": 6954442,
- "confidential": false,
- "diff_discussion": false,
- "expanded": false,
- "for_commit": false,
- "individual_note": true,
- "resolvable": false,
- "resolved_by_push": false,
- "notes": [
- {
- "id": "76489845",
- "author": {
- "id": 1675733,
- "username": "gitlab-qa-bot",
- "name": "gitlab-qa-bot",
- "state": "active",
- "avatar_url": "https://secure.gravatar.com/avatar/8355ad0f2761367fae6b9c4fe80994b9?s=80&d=identicon",
- "show_status": false,
- "path": "/gitlab-qa-bot"
- },
- "created_at": "2022-02-22T07:06:55.038Z",
- "updated_at": "2022-02-22T07:06:55.038Z",
- "system": false,
- "noteable_id": 8333422,
- "noteable_type": "MergeRequest",
- "resolvable": false,
- "resolved": true,
- "confidential": false,
- "noteable_iid": 1,
- "note": "This is a test comment",
- "note_html": "<p data-sourcepos=\"1:1-1:22\" dir=\"auto\">This is a test comment</p>",
- "current_user": {
- "can_edit": true,
- "can_award_emoji": true,
- "can_resolve": false,
- "can_resolve_discussion": false
- },
- "is_noteable_author": true,
- "discussion_id": "fd73763cbcbf7b29eb8765d969a38f7d735e222a",
- "emoji_awardable": true,
- "report_abuse_path": "/gitlab-qa-bot/...",
- "noteable_note_url": "https://staging.gitlab.com/gitlab-qa-bot/...",
- "cached_markdown_version": 1900552,
- "human_access": "Maintainer",
- "is_contributor": false,
- "project_name": "contract-testing",
- "path": "/gitlab-qa-bot/..."
- }
- ],
- "resolved": true
- }
- ],
- "matchingRules": {
- "$.body": {
- "min": 1
- },
- "$.body[*].*": {
- "match": "type"
- },
- "$.body[*].id": {
- "match": "type"
- },
- "$.body[*].reply_id": {
- "match": "type"
- },
- "$.body[*].project_id": {
- "match": "type"
- },
- "$.body[*].confidential": {
- "match": "type"
- },
- "$.body[*].diff_discussion": {
- "match": "type"
- },
- "$.body[*].expanded": {
- "match": "type"
- },
- "$.body[*].for_commit": {
- "match": "type"
- },
- "$.body[*].individual_note": {
- "match": "type"
- },
- "$.body[*].resolvable": {
- "match": "type"
- },
- "$.body[*].resolved_by_push": {
- "match": "type"
- },
- "$.body[*].notes": {
- "min": 1
- },
- "$.body[*].notes[*].*": {
- "match": "type"
- },
- "$.body[*].notes[*].id": {
- "match": "type"
- },
- "$.body[*].notes[*].author.id": {
- "match": "type"
- },
- "$.body[*].notes[*].author.username": {
- "match": "type"
- },
- "$.body[*].notes[*].author.name": {
- "match": "type"
- },
- "$.body[*].notes[*].author.state": {
- "match": "type"
- },
- "$.body[*].notes[*].author.avatar_url": {
- "match": "type"
- },
- "$.body[*].notes[*].author.show_status": {
- "match": "type"
- },
- "$.body[*].notes[*].author.path": {
- "match": "type"
- },
- "$.body[*].notes[*].created_at": {
- "match": "regex",
- "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
- },
- "$.body[*].notes[*].updated_at": {
- "match": "regex",
- "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
- },
- "$.body[*].notes[*].system": {
- "match": "type"
- },
- "$.body[*].notes[*].noteable_id": {
- "match": "type"
- },
- "$.body[*].notes[*].noteable_type": {
- "match": "type"
- },
- "$.body[*].notes[*].resolvable": {
- "match": "type"
- },
- "$.body[*].notes[*].resolved": {
- "match": "type"
- },
- "$.body[*].notes[*].confidential": {
- "match": "type"
- },
- "$.body[*].notes[*].noteable_iid": {
- "match": "type"
- },
- "$.body[*].notes[*].note": {
- "match": "type"
- },
- "$.body[*].notes[*].note_html": {
- "match": "type"
- },
- "$.body[*].notes[*].current_user.can_edit": {
- "match": "type"
- },
- "$.body[*].notes[*].current_user.can_award_emoji": {
- "match": "type"
- },
- "$.body[*].notes[*].current_user.can_resolve": {
- "match": "type"
- },
- "$.body[*].notes[*].current_user.can_resolve_discussion": {
- "match": "type"
- },
- "$.body[*].notes[*].is_noteable_author": {
- "match": "type"
- },
- "$.body[*].notes[*].discussion_id": {
- "match": "type"
- },
- "$.body[*].notes[*].emoji_awardable": {
- "match": "type"
- },
- "$.body[*].notes[*].report_abuse_path": {
- "match": "type"
- },
- "$.body[*].notes[*].noteable_note_url": {
- "match": "type"
- },
- "$.body[*].notes[*].cached_markdown_version": {
- "match": "type"
- },
- "$.body[*].notes[*].human_access": {
- "match": "type"
- },
- "$.body[*].notes[*].is_contributor": {
- "match": "type"
- },
- "$.body[*].notes[*].project_name": {
- "match": "type"
- },
- "$.body[*].notes[*].path": {
- "match": "type"
- },
- "$.body[*].resolved": {
- "match": "type"
- }
- }
- }
- }
- ],
- "metadata": {
- "pactSpecification": {
- "version": "2.0.0"
- }
- }
-} \ No newline at end of file
diff --git a/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json b/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json
deleted file mode 100644
index 4b6cab0fc94..00000000000
--- a/qa/contracts/contracts/merge_request_page-merge_request_metadata_endpoint.json
+++ /dev/null
@@ -1,222 +0,0 @@
-{
- "consumer": {
- "name": "Merge Request Page"
- },
- "provider": {
- "name": "Merge Request Metadata Endpoint"
- },
- "interactions": [
- {
- "description": "a request for Metadata",
- "request": {
- "method": "GET",
- "path": "/diffs_metadata.json",
- "headers": {
- "Accept": "*/*"
- }
- },
- "response": {
- "status": 200,
- "headers": {
- "Content-Type": "application/json; charset=utf-8"
- },
- "body": {
- "real_size": "1",
- "size": 1,
- "branch_name": "testing-branch-1",
- "source_branch_exists": true,
- "target_branch_name": "master",
- "merge_request_diff": {
- "created_at": "2022-02-17T11:47:08.804Z",
- "commits_count": 1,
- "latest": true,
- "short_commit_sha": "aee1ffec",
- "base_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
- "head_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true",
- "version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
- "compare_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f"
- },
- "latest_diff": true,
- "latest_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs",
- "added_lines": 1,
- "removed_lines": 1,
- "render_overflow_warning": false,
- "email_patch_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1.patch",
- "plain_diff_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1.diff",
- "merge_request_diffs": [
- {
- "commits_count": 1,
- "latest": true,
- "short_commit_sha": "aee1ffec",
- "base_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
- "head_version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_head=true",
- "version_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773",
- "compare_path": "/gitlab-qa-bot/contract-testing/-/merge_requests/1/diffs?diff_id=10581773&start_sha=aee1ffec2299c0cfb17c8821e931339b73a3759f"
- }
- ],
- "definition_path_prefix": "/gitlab-qa-bot/contract-testing/-/blob/aee1ffec2299c0cfb17c8821e931339b73a3759f",
- "diff_files": [
- {
- "added_lines": 1,
- "removed_lines": 1,
- "new_path": "Gemfile",
- "old_path": "Gemfile",
- "new_file": false,
- "deleted_file": false,
- "submodule": false,
- "file_identifier_hash": "67d82b8716a5b6c52c7abf0b2cd99c7594ed3587",
- "file_hash": "de3150c01c3a946a6168173c4116741379fe3579"
- }
- ],
- "has_conflicts": false,
- "can_merge": false,
- "project_path": "gitlab-qa-bot/contract-testing",
- "project_name": "contract-testing"
- },
- "matchingRules": {
- "$.body.real_size": {
- "match": "type"
- },
- "$.body.size": {
- "match": "type"
- },
- "$.body.branch_name": {
- "match": "type"
- },
- "$.body.source_branch_exists": {
- "match": "type"
- },
- "$.body.target_branch_name": {
- "match": "type"
- },
- "$.body.merge_request_diff.created_at": {
- "match": "regex",
- "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d(:?[0-5]\\d)?|Z)$"
- },
- "$.body.merge_request_diff.commits_count": {
- "match": "type"
- },
- "$.body.merge_request_diff.latest": {
- "match": "type"
- },
- "$.body.merge_request_diff.short_commit_sha": {
- "match": "type"
- },
- "$.body.merge_request_diff.base_version_path": {
- "match": "type"
- },
- "$.body.merge_request_diff.head_version_path": {
- "match": "type"
- },
- "$.body.merge_request_diff.version_path": {
- "match": "type"
- },
- "$.body.merge_request_diff.compare_path": {
- "match": "type"
- },
- "$.body.latest_diff": {
- "match": "type"
- },
- "$.body.latest_version_path": {
- "match": "type"
- },
- "$.body.added_lines": {
- "match": "type"
- },
- "$.body.removed_lines": {
- "match": "type"
- },
- "$.body.render_overflow_warning": {
- "match": "type"
- },
- "$.body.email_patch_path": {
- "match": "type"
- },
- "$.body.plain_diff_path": {
- "match": "type"
- },
- "$.body.merge_request_diffs": {
- "min": 1
- },
- "$.body.merge_request_diffs[*].*": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].commits_count": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].latest": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].short_commit_sha": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].base_version_path": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].head_version_path": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].version_path": {
- "match": "type"
- },
- "$.body.merge_request_diffs[*].compare_path": {
- "match": "type"
- },
- "$.body.definition_path_prefix": {
- "match": "type"
- },
- "$.body.diff_files": {
- "min": 1
- },
- "$.body.diff_files[*].*": {
- "match": "type"
- },
- "$.body.diff_files[*].added_lines": {
- "match": "type"
- },
- "$.body.diff_files[*].removed_lines": {
- "match": "type"
- },
- "$.body.diff_files[*].new_path": {
- "match": "type"
- },
- "$.body.diff_files[*].old_path": {
- "match": "type"
- },
- "$.body.diff_files[*].new_file": {
- "match": "type"
- },
- "$.body.diff_files[*].deleted_file": {
- "match": "type"
- },
- "$.body.diff_files[*].submodule": {
- "match": "type"
- },
- "$.body.diff_files[*].file_identifier_hash": {
- "match": "type"
- },
- "$.body.diff_files[*].file_hash": {
- "match": "type"
- },
- "$.body.has_conflicts": {
- "match": "type"
- },
- "$.body.can_merge": {
- "match": "type"
- },
- "$.body.project_path": {
- "match": "type"
- },
- "$.body.project_name": {
- "match": "type"
- }
- }
- }
- }
- ],
- "metadata": {
- "pactSpecification": {
- "version": "2.0.0"
- }
- }
-} \ No newline at end of file
diff --git a/qa/contracts/provider/environments/base.rb b/qa/contracts/provider/environments/base.rb
deleted file mode 100644
index 695ee6b867d..00000000000
--- a/qa/contracts/provider/environments/base.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-module Provider
- module Environments
- class Base
- attr_writer :base_url, :merge_request
-
- def call(env)
- @payload
- end
-
- def http(endpoint)
- Faraday.default_adapter = :net_http
- response = Faraday.get(@base_url + endpoint)
- @payload = [response.status, response.headers, [response.body]]
- self
- end
-
- def merge_request(endpoint)
- http(@merge_request + endpoint) if endpoint.include? '.json'
- end
- end
- end
-end
diff --git a/qa/contracts/provider/environments/local.rb b/qa/contracts/provider/environments/local.rb
deleted file mode 100644
index 0d472bc25e9..00000000000
--- a/qa/contracts/provider/environments/local.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module Provider
- module Environments
- class Local < Base
- def initialize
- @base_url = ENV['CONTRACT_HOST']
- @merge_request = ENV['CONTRACT_MR']
- end
- end
- end
-end
diff --git a/qa/contracts/provider/spec/diffs_helper.rb b/qa/contracts/provider/spec/diffs_helper.rb
deleted file mode 100644
index 95dbc4254e6..00000000000
--- a/qa/contracts/provider/spec/diffs_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../spec_helper'
-
-module Provider
- module DiffsHelper
- local = Environments::Local.new
-
- Pact.service_provider "Merge Request Diffs Endpoint" do
- app { local.merge_request('/diffs_batch.json?page=0') }
-
- honours_pact_with 'Merge Request Page' do
- pact_uri '../contracts/merge_request_page-merge_request_diffs_endpoint.json'
- end
- end
- end
-end
diff --git a/qa/contracts/provider/spec/discussions_helper.rb b/qa/contracts/provider/spec/discussions_helper.rb
deleted file mode 100644
index 642dde79e1d..00000000000
--- a/qa/contracts/provider/spec/discussions_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../spec_helper'
-
-module Provider
- module DiscussionsHelper
- local = Environments::Local.new
-
- Pact.service_provider "Merge Request Discussions Endpoint" do
- app { local.merge_request('/discussions.json') }
-
- honours_pact_with 'Merge Request Page' do
- pact_uri '../contracts/merge_request_page-merge_request_discussions_endpoint.json'
- end
- end
- end
-end
diff --git a/qa/contracts/provider/spec/metadata_helper.rb b/qa/contracts/provider/spec/metadata_helper.rb
deleted file mode 100644
index a3eb4978641..00000000000
--- a/qa/contracts/provider/spec/metadata_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../spec_helper'
-
-module Provider
- module MetadataHelper
- local = Environments::Local.new
-
- Pact.service_provider "Merge Request Metadata Endpoint" do
- app { local.merge_request('/diffs_metadata.json') }
-
- honours_pact_with 'Merge Request Page' do
- pact_uri '../contracts/merge_request_page-merge_request_metadata_endpoint.json'
- end
- end
- end
-end
diff --git a/qa/contracts/provider/spec_helper.rb b/qa/contracts/provider/spec_helper.rb
deleted file mode 100644
index 1869c039910..00000000000
--- a/qa/contracts/provider/spec_helper.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module SpecHelper
- unless ENV['CONTRACT_HOST']
- raise(ArgumentError, 'Contract tests require CONTRACT_HOST environment variable to be set!')
- end
-
- require_relative '../../../config/bundler_setup'
- Bundler.require(:default)
-
- root = File.expand_path('../', __dir__)
-
- loader = Zeitwerk::Loader.new
- loader.push_dir(root)
-
- loader.ignore("#{root}/consumer")
- loader.ignore("#{root}/contracts")
-
- loader.collapse("#{root}/provider/spec")
-
- loader.setup
-end
diff --git a/qa/lib/gitlab/page/group/settings/usage_quotas.rb b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
index df12fe4076c..2b491188595 100644
--- a/qa/lib/gitlab/page/group/settings/usage_quotas.rb
+++ b/qa/lib/gitlab/page/group/settings/usage_quotas.rb
@@ -5,13 +5,15 @@ module Gitlab
module Group
module Settings
class UsageQuotas < Chemlab::Page
- link :pipeline_tab, id: 'pipelines-quota'
- link :storage_tab, id: 'storage-quota'
- link :buy_ci_minutes, text: 'Buy additional minutes'
- link :buy_storage, text: /Purchase more storage/
+ # TODO: Supplant with data-qa-selectors
+ link :pipelines_tab
+ link :storage_tab
+ link :buy_ci_minutes
+ link :buy_storage
div :plan_ci_minutes
div :additional_ci_minutes
span :purchased_usage_total
+ div :purchased_usage_total_free, 'data-testid': 'purchased-usage-card' # Different UI for free namespace
div :ci_purchase_successful_alert, text: /You have successfully purchased CI minutes/
div :storage_purchase_successful_alert, text: /You have successfully purchased a storage/
h2 :storage_available_alert, text: /purchased storage is available/
@@ -36,9 +38,14 @@ module Gitlab
# Returns total purchased storage value once it's ready on page
#
# @return [Float] Total purchased storage value in GiB
- def total_purchased_storage
+ def total_purchased_storage(free_name_space = true)
storage_available_alert_element.wait_until(&:present?)
- purchased_usage_total.to_f
+
+ if free_name_space
+ purchased_usage_total_free.split('/').last.match(/\d+\.\d+/)[0].to_f
+ else
+ purchased_usage_total.to_f
+ end
end
end
end
diff --git a/qa/qa/flow/purchase.rb b/qa/qa/flow/purchase.rb
index 41d771b9b6d..5558e177685 100644
--- a/qa/qa/flow/purchase.rb
+++ b/qa/qa/flow/purchase.rb
@@ -26,7 +26,7 @@ module QA
def purchase_ci_minutes(quantity: 1)
Page::Group::Menu.perform(&:go_to_usage_quotas)
Gitlab::Page::Group::Settings::UsageQuotas.perform do |usage_quota|
- usage_quota.pipeline_tab
+ usage_quota.pipelines_tab
usage_quota.buy_ci_minutes
end
@@ -48,6 +48,9 @@ module QA
usage_quota.buy_storage
end
+ # Purchase checkout opens a new tab
+ Chemlab.configuration.browser.session.engine.switch_window
+
Gitlab::Page::Subscriptions::New.perform do |storage|
storage.quantity = quantity
storage.continue_to_billing
diff --git a/qa/qa/mobile/page/profile/menu.rb b/qa/qa/mobile/page/profile/menu.rb
deleted file mode 100644
index 34c53a95e03..00000000000
--- a/qa/qa/mobile/page/profile/menu.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Mobile
- module Page
- module Profile
- module Menu
- extend QA::Page::PageConcern
-
- def self.prepended(base)
- super
-
- base.class_eval do
- prepend QA::Mobile::Page::Main::Menu
- end
- end
-
- def within_sidebar
- open_mobile_nav_sidebar
- super
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/mobile/page/sub_menus/common.rb b/qa/qa/mobile/page/sub_menus/common.rb
index 6a0477a3f31..aaa2de270b0 100644
--- a/qa/qa/mobile/page/sub_menus/common.rb
+++ b/qa/qa/mobile/page/sub_menus/common.rb
@@ -6,10 +6,10 @@ module QA
module SubMenus
module Common
def open_mobile_nav_sidebar
- if has_element?(:project_sidebar, visible: false)
+ unless has_css?('.sidebar-expanded-mobile')
Support::Retrier.retry_until do
click_element(:toggle_mobile_nav_button)
- has_element?(:project_sidebar, visible: true)
+ has_css?('.sidebar-expanded-mobile')
end
end
end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 248c5f38438..775a5ead5f7 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -5,10 +5,12 @@ require 'capybara/dsl'
module QA
module Page
class Base
- prepend Support::Page::Logging if Runtime::Env.debug?
+ prepend Support::Page::Logging
+
include Capybara::DSL
include Scenario::Actable
include Support::WaitForRequests
+
extend Validatable
extend SingleForwardable
diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb
index 6a9249621e1..28ca77aba24 100644
--- a/qa/qa/page/component/access_tokens.rb
+++ b/qa/qa/page/component/access_tokens.rb
@@ -9,6 +9,10 @@ module QA
def self.included(base)
super
+ base.class_eval do
+ include QA::Page::Component::ConfirmModal
+ end
+
base.view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do
element :expiry_date_field
end
@@ -22,11 +26,11 @@ module QA
element :api_label, '#{scope}_label' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
- base.view 'app/views/shared/access_tokens/_created_container.html.haml' do
+ base.view 'app/assets/javascripts/access_tokens/components/new_access_token_app.vue' do
element :created_access_token
end
- base.view 'app/views/shared/access_tokens/_table.html.haml' do
+ base.view 'app/assets/javascripts/access_tokens/components/access_token_table_app.vue' do
element :revoke_button
end
end
@@ -64,10 +68,10 @@ module QA
def revoke_first_token_with_name(token_name)
within first_token_row_for_name(token_name) do
- accept_confirm do
- click_element(:revoke_button)
- end
+ click_element(:revoke_button)
end
+
+ click_confirmation_ok_button
end
end
end
diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb
index 7c536ff651b..18747c19aee 100644
--- a/qa/qa/page/component/invite_members_modal.rb
+++ b/qa/qa/page/component/invite_members_modal.rb
@@ -27,7 +27,7 @@ module QA
element :invite_a_group_button
end
- base.view 'app/assets/javascripts/invite_members/components/invite_members_trigger.vue' do
+ base.view 'app/assets/javascripts/invite_members/constants.js' do
element :invite_members_button
end
end
diff --git a/qa/qa/page/component/snippet.rb b/qa/qa/page/component/snippet.rb
index 47d32ae8225..47ed1a9616b 100644
--- a/qa/qa/page/component/snippet.rb
+++ b/qa/qa/page/component/snippet.rb
@@ -9,6 +9,10 @@ module QA
def self.included(base)
super
+ base.class_eval do
+ include QA::Page::Component::ConfirmModal
+ end
+
base.view 'app/assets/javascripts/snippets/components/snippet_title.vue' do
element :snippet_title_content
end
@@ -224,9 +228,8 @@ module QA
def delete_comment(comment)
click_element(:more_actions_dropdown)
- accept_alert do
- click_element(:delete_comment_button)
- end
+ click_element(:delete_comment_button)
+ click_confirmation_ok_button
unless has_no_element?(:note_content, text: comment)
raise ElementNotFound, "Comment was not removed as expected"
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 2cd78f9f17a..b057a27fa3e 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -5,6 +5,7 @@ module QA
module Group
class Show < Page::Base
include Page::Component::GroupsFilter
+ include QA::Page::Component::ConfirmModal
view 'app/views/groups/_home_panel.html.haml' do
element :new_project_button
@@ -46,9 +47,8 @@ module QA
end
def leave_group
- accept_alert do
- click_element :leave_group_link
- end
+ click_element :leave_group_link
+ click_confirmation_ok_button
end
end
end
diff --git a/qa/qa/page/issuable/new.rb b/qa/qa/page/issuable/new.rb
index c549190c65b..ca1219cb7fc 100644
--- a/qa/qa/page/issuable/new.rb
+++ b/qa/qa/page/issuable/new.rb
@@ -58,6 +58,8 @@ module QA
click_element :issuable_label
click_link label.title
+
+ click_element :issuable_label # So that the dropdown goes away(click away action)
end
def assign_to_me
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 8f5ac62d127..a0bebf6bd7a 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -19,6 +19,10 @@ module QA
element :review_bar_content
end
+ view 'app/assets/javascripts/batch_comments/components/draft_note.vue' do
+ element :draft_note_content
+ end
+
view 'app/assets/javascripts/diffs/components/compare_dropdown_layout.vue' do
element :dropdown_content
end
@@ -78,9 +82,9 @@ module QA
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
element :merge_button
- element :fast_forward_message_content
element :merge_moment_dropdown
element :merge_immediately_menu_item
+ element :merged_status_content
end
view 'app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue' do
@@ -115,12 +119,9 @@ module QA
element :description_content
end
- view 'app/views/projects/merge_requests/_mr_box.html.haml' do
- element :title_content, required: true
- end
-
view 'app/views/projects/merge_requests/_mr_title.html.haml' do
element :edit_button
+ element :title_content, required: true
end
view 'app/views/projects/merge_requests/show.html.haml' do
@@ -153,6 +154,8 @@ module QA
has_element?(:submit_review_button)
within_element(:review_bar_content) do
click_element(:review_preview_dropdown)
+ end
+ within_element(:draft_note_content) do
click_element(:submit_review_button)
end
# After clicking the button, wait for it to disappear
@@ -192,10 +195,6 @@ module QA
click_element(:edit_button)
end
- def fast_forward_possible?
- has_element?(:fast_forward_message_content)
- end
-
def fast_forward_not_possible?
has_element?(:no_fast_forward_message_content)
end
@@ -265,7 +264,7 @@ module QA
# To remove page refresh logic if possible
# 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)
+ has_element?(:merged_status_content, text: /The changes were merged into|Changes merged into/, wait: 20)
end
end
@@ -316,12 +315,6 @@ module QA
end
click_element(:mr_rebase_button)
-
- success = wait_until do
- fast_forward_possible?
- end
-
- raise "Rebase did not appear to be successful" unless success
end
def merge_immediately!
diff --git a/qa/qa/page/profile/emails.rb b/qa/qa/page/profile/emails.rb
index c20bc6a5c57..f8aeea50513 100644
--- a/qa/qa/page/profile/emails.rb
+++ b/qa/qa/page/profile/emails.rb
@@ -4,6 +4,8 @@ module QA
module Page
module Profile
class Emails < Page::Base
+ include QA::Page::Component::ConfirmModal
+
view 'app/views/profiles/emails/index.html.haml' do
element :email_address_field
element :add_email_address_button
@@ -17,11 +19,10 @@ module QA
end
def delete_email_address(email_address)
- page.accept_alert do
- within_element(:email_row_content, text: email_address) do
- click_element(:delete_email_link)
- end
+ within_element(:email_row_content, text: email_address) do
+ click_element(:delete_email_link)
end
+ click_confirmation_ok_button
end
end
end
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index d638a378610..947fa2fec0f 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -6,7 +6,7 @@ module QA
class Menu < Page::Base
# We need to check remote_mobile_device_name instead of mobile_layout? here
# since tablets have the regular top navigation bar but still close the left nav
- prepend QA::Mobile::Page::Profile::Menu if QA::Runtime::Env.remote_mobile_device_name
+ prepend QA::Mobile::Page::SubMenus::Common if QA::Runtime::Env.remote_mobile_device_name
view 'app/views/layouts/nav/sidebar/_profile.html.haml' do
element :access_token_link, 'link_to profile_personal_access_tokens_path' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
index a19fcf8ec6e..4bf8abb555b 100644
--- a/qa/qa/page/project/branches/show.rb
+++ b/qa/qa/page/project/branches/show.rb
@@ -5,6 +5,8 @@ module QA
module Project
module Branches
class Show < Page::Base
+ include Page::Component::ConfirmModal
+
view 'app/assets/javascripts/branches/components/delete_branch_button.vue' do
element :delete_branch_button
end
@@ -54,9 +56,8 @@ module QA
end
def delete_merged_branches
- accept_alert do
- click_element(:delete_merged_branches)
- end
+ click_element(:delete_merged_branches)
+ click_confirmation_ok_button
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index 0d495fc661e..7d162c6e48f 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -79,12 +79,6 @@ module QA
def has_no_issue?(issue)
has_no_element? :issuable_container, issuable_title: issue.title
end
-
- def wait_for_vue_issues_list_ff
- Support::Retrier.retry_until(max_duration: 60, reload_page: page, retry_on_exception: true, sleep_interval: 5) do
- find_element(:closed_issuables_tab)
- end
- end
end
end
end
diff --git a/qa/qa/page/project/members.rb b/qa/qa/page/project/members.rb
index 30748ed920b..4692f3621b8 100644
--- a/qa/qa/page/project/members.rb
+++ b/qa/qa/page/project/members.rb
@@ -15,7 +15,7 @@ module QA
element :invite_a_group_button
end
- view 'app/assets/javascripts/invite_members/components/invite_members_trigger.vue' do
+ view 'app/assets/javascripts/invite_members/constants.js' do
element :invite_members_button
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 26fff85dd99..7da763ca0e6 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -91,7 +91,7 @@ module QA
end
def click_repo_by_url_link
- click_button 'Repo by URL'
+ click_button 'Repository by URL'
end
def disable_initialize_with_readme
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 3f1cc2f1257..06d154f5178 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -24,6 +24,7 @@ module QA
view 'app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue' do
element :expand_linked_pipeline_button
element :linked_pipeline_container
+ element :downstream_title_content
end
view 'app/assets/javascripts/reports/components/report_section.vue' do
@@ -73,6 +74,18 @@ module QA
end
end
+ def linked_pipelines
+ all_elements(:linked_pipeline_container, minimum: 1)
+ end
+
+ def find_linked_pipeline_by_title(title)
+ linked_pipelines.find do |pipeline|
+ within(pipeline) do
+ find_element(:downstream_title_content).text.include?(title)
+ end
+ end
+ end
+
def has_linked_pipeline?(title: nil)
# If the pipeline page has loaded linked pipelines should appear, but it can take a little while,
# especially on busier environments.
@@ -89,21 +102,6 @@ module QA
alias_method :has_no_child_pipeline?, :has_no_linked_pipeline?
- def click_job(job_name)
- # Retry due to transient bug https://gitlab.com/gitlab-org/gitlab/-/issues/347126
- QA::Support::Retrier.retry_on_exception do
- click_element(:job_link, Project::Job::Show, text: job_name)
- end
- end
-
- def linked_pipelines
- all_elements(:linked_pipeline_container, minimum: 1)
- end
-
- def find_linked_pipeline_by_title(title)
- linked_pipelines.find { |pipeline| pipeline[:title].include?(title) }
- end
-
def expand_linked_pipeline(title: nil)
linked_pipeline = title ? find_linked_pipeline_by_title(title) : linked_pipelines.first
@@ -124,6 +122,13 @@ module QA
first('.js-pipeline-graph-job-link', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME).click
end
+ def click_job(job_name)
+ # Retry due to transient bug https://gitlab.com/gitlab-org/gitlab/-/issues/347126
+ QA::Support::Retrier.retry_on_exception do
+ click_element(:job_link, Project::Job::Show, text: job_name)
+ end
+ end
+
def click_job_action(job_name)
wait_for_requests
diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb
index 197fd8fd9fb..78f16a8a65c 100644
--- a/qa/qa/page/project/pipeline_editor/show.rb
+++ b/qa/qa/page/project/pipeline_editor/show.rb
@@ -66,7 +66,6 @@ module QA
end
def open_branch_selector_dropdown
- dismiss_file_tree_popover if has_element?(:file_tree_popover, wait: 1)
click_element(:branch_selector_button)
end
diff --git a/qa/qa/page/project/registry/show.rb b/qa/qa/page/project/registry/show.rb
index 270445560be..95850f34962 100644
--- a/qa/qa/page/project/registry/show.rb
+++ b/qa/qa/page/project/registry/show.rb
@@ -11,10 +11,8 @@ module QA
view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
element :more_actions_menu
- end
-
- view 'app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue' do
element :tag_delete_button
+ element :tag_name_content
end
def has_registry_repository?(name)
@@ -26,11 +24,11 @@ module QA
end
def has_tag?(tag_name)
- has_button?(tag_name)
+ has_element?(:tag_name_content, text: tag_name)
end
def has_no_tag?(tag_name)
- has_no_button?(tag_name)
+ has_no_element?(:tag_name_content, text: tag_name)
end
def click_delete
diff --git a/qa/qa/page/project/settings/access_tokens.rb b/qa/qa/page/project/settings/access_tokens.rb
index d559ca4daaa..47afa26191c 100644
--- a/qa/qa/page/project/settings/access_tokens.rb
+++ b/qa/qa/page/project/settings/access_tokens.rb
@@ -8,6 +8,7 @@ module QA
module Settings
class AccessTokens < Page::Base
include Page::Component::AccessTokens
+ include Page::Component::ConfirmModal
end
end
end
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 525210a08f6..fd9cc8a13e7 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Settings
class Advanced < Page::Base
- include Component::ConfirmModal
+ include QA::Page::Component::ConfirmModal
include Component::NamespaceSelect
view 'app/views/projects/edit.html.haml' do
@@ -65,15 +65,13 @@ module QA
end
def archive_project
- page.accept_alert("Are you sure that you want to archive this project?") do
- click_element :archive_project_link
- end
+ click_element :archive_project_link
+ click_confirmation_ok_button
end
def unarchive_project
- page.accept_alert("Are you sure that you want to unarchive this project?") do
- click_element :unarchive_project_link
- end
+ click_element :unarchive_project_link
+ click_confirmation_ok_button
end
end
end
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index 2b8fad64afb..7ee015ceb98 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -14,14 +14,14 @@ module QA
element :ci_variable_delete_button
end
- view 'app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue' do
+ view 'app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_table.vue' do
element :ci_variable_table_content
element :add_ci_variable_button
element :edit_ci_variable_button
element :reveal_ci_variable_value_button
end
- def fill_variable(key, value, masked)
+ def fill_variable(key, value, masked = false)
within_element(:ci_variable_key_field) { find('input').set key }
fill_element :ci_variable_value_field, value
click_ci_variable_save_button
diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb
index 0d5d4df9f34..dd9c94ebbb7 100644
--- a/qa/qa/page/project/settings/merge_request.rb
+++ b/qa/qa/page/project/settings/merge_request.rb
@@ -24,7 +24,7 @@ module QA
end
def enable_ff_only
- choose_element(:merge_ff_radio)
+ choose_element(:merge_ff_radio, true)
click_save_changes
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 435cc4a717e..3901eff0bcb 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -222,7 +222,7 @@ module QA
# expected visibility.
commit_success = retry_until(sleep_interval: 5) do
within_element(:commit_to_current_branch_radio_container) do
- choose_element(:commit_type_radio)
+ choose_element(:commit_type_radio, true)
end
click_element(:commit_button) if has_element?(:commit_button)
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 79cb1ebebc9..667dbc03fc3 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -118,7 +118,7 @@ module QA
MSG
end
- body[:id] = body.fetch(:id).split('/').last
+ body[:id] = body.fetch(:id).split('/').last if body.key?(:id)
body.transform_keys { |key| key.to_s.underscore.to_sym }
else
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index fc7f8445d4e..ba1b581b100 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -99,13 +99,13 @@ module QA
fabrication_time: fabrication_time
)
- Runtime::Logger.debug do
+ Runtime::Logger.info do
msg = ["==#{'=' * parents.size}>"]
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 #{fabrication_method}"
- msg << "in #{fabrication_time} seconds"
+ msg << "in #{fabrication_time.round(2)} seconds"
msg.compact.join(' ')
end
@@ -161,7 +161,7 @@ module QA
end
def visit!(skip_resp_code_check: false)
- Runtime::Logger.debug("Visiting #{Rainbow(self.class.name).black.bg(:white)} at #{web_url}")
+ Runtime::Logger.info("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)
@@ -224,7 +224,7 @@ module QA
def remove_via_api!
super
- Runtime::Logger.debug(["Removed a #{self.class.name}", identifier].compact.join(' '))
+ Runtime::Logger.info(["Removed a #{self.class.name}", identifier].compact.join(' '))
end
protected
diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb
index 0061f74cec5..d9300f80f5d 100644
--- a/qa/qa/resource/members.rb
+++ b/qa/qa/resource/members.rb
@@ -9,7 +9,7 @@ module QA
module Members
def add_member(user, access_level = AccessLevel::DEVELOPER)
Support::Retrier.retry_until do
- QA::Runtime::Logger.debug(%Q[Adding user #{user.username} to #{full_path} #{self.class.name}])
+ QA::Runtime::Logger.info(%(Adding user #{user.username} to #{full_path} #{self.class.name}))
response = post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
break true if response.code == QA::Support::API::HTTP_STATUS_CREATED
@@ -18,7 +18,7 @@ module QA
end
def remove_member(user)
- QA::Runtime::Logger.debug(%Q[Removing user #{user.username} from #{full_path} #{self.class.name}])
+ QA::Runtime::Logger.info(%(Removing user #{user.username} from #{full_path} #{self.class.name}))
delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url
end
@@ -29,7 +29,7 @@ module QA
def invite_group(group, access_level = AccessLevel::GUEST)
Support::Retrier.retry_until do
- QA::Runtime::Logger.debug(%Q[Sharing #{self.class.name} with #{group.name}])
+ QA::Runtime::Logger.info(%(Sharing #{self.class.name} with #{group.name}))
response = post Runtime::API::Request.new(api_client, api_share_path).url, { group_id: group.id, group_access: access_level }
response.code == QA::Support::API::HTTP_STATUS_CREATED
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 2db4f4b5f65..59964c5833d 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -7,14 +7,15 @@ module QA
include Members
include Visibility
- attr_accessor :repository_storage, # requires admin access
- :initialize_with_readme,
+ attr_accessor :initialize_with_readme,
:auto_devops_enabled,
:github_personal_access_token,
:github_repository_path,
:gitlab_repository_path,
:personal_namespace
+ attr_reader :repository_storage
+
attributes :id,
:name,
:path,
@@ -70,6 +71,15 @@ module QA
@name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name
end
+ # Sets the project's repository storage
+ # This feature requires admin access so be sure to fabricate the project as an admin user, and add the metadata
+ # `:requires_admin` to the test it's used in.
+ def repository_storage=(name)
+ raise ArgumentError, "Please provide a valid repository storage name" if name.to_s.empty?
+
+ @repository_storage = name
+ end
+
def fabricate!
return if @import
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index c014563671d..278bdd1cabd 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -90,21 +90,19 @@ module QA
"/runners/#{id}"
end
- def api_get_path
- end
+ def api_get_path; end
def api_post_path
"/runners"
end
- def api_post_body
- end
+ def api_post_body; end
private
def dump_logs
if @docker_container.running?
- @docker_container.logs { |line| QA::Runtime::Logger.debug(line) }
+ @docker_container.logs
else
QA::Runtime::Logger.debug("No runner container found named #{name}")
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 0db5314de4d..8ad5b107c08 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -61,10 +61,6 @@ module QA
ENV['CI_PROJECT_NAME']
end
- def debug?
- enabled?(ENV['QA_DEBUG'], default: false)
- end
-
def generate_allure_report?
enabled?(ENV['QA_GENERATE_ALLURE_REPORT'], default: false)
end
@@ -73,10 +69,6 @@ module QA
ENV['QA_DEFAULT_BRANCH'] || 'main'
end
- def log_destination
- ENV['QA_LOG_PATH'] || $stdout
- end
-
def colorized_logs?
enabled?(ENV['COLORIZED_LOGS'], default: false)
end
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
index 93e215e9635..db3a59b0549 100644
--- a/qa/qa/runtime/feature.rb
+++ b/qa/qa/runtime/feature.rb
@@ -9,6 +9,8 @@ module QA
AuthorizationError = Class.new(RuntimeError)
UnknownScopeError = Class.new(RuntimeError)
UnknownStateError = Class.new(RuntimeError)
+ UnknownFeatureFlagError = Class.new(RuntimeError)
+ DefinitionFileError = Class.new(RuntimeError)
class << self
# Documentation: https://docs.gitlab.com/ee/api/features.html
@@ -52,7 +54,21 @@ module QA
def enabled?(key, **scopes)
feature = JSON.parse(get_features).find { |flag| flag['name'] == key.to_s }
- feature && (feature['state'] == 'on' || feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes))
+ if feature
+ feature['state'] == 'on' ||
+ feature['state'] == 'conditional' && scopes.present? && enabled_scope?(feature['gates'], **scopes)
+ else
+ # The feature wasn't found via the API so we check for a default value.
+ file = Pathname.new('../config/feature_flags')
+ .expand_path(Runtime::Path.qa_root)
+ .glob("**/#{key}.yml")
+ .first
+
+ raise UnknownFeatureFlagError, "No feature flag found named '#{key}'" unless file
+
+ definition = YAML.safe_load(File.read(file))
+ definition['default_enabled'].to_s.casecmp('true') == 0
+ end
end
private
diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb
index 1f17146303a..e0e7385d6d4 100644
--- a/qa/qa/runtime/logger.rb
+++ b/qa/qa/runtime/logger.rb
@@ -9,10 +9,14 @@ module QA
def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unknown
+ # Global logger instance
+ #
+ # @return [ActiveSupport::Logger]
def self.logger
@logger ||= Gitlab::QA::TestLogger.logger(
- level: Runtime::Env.debug? ? ::Logger::DEBUG : ::Logger::INFO,
- source: 'QA Tests'
+ level: Gitlab::QA::Runtime::Env.log_level,
+ source: 'QA Tests',
+ path: File.expand_path('../../tmp', __dir__)
)
end
end
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
index 6b4cbe6af6e..67d048b6cc1 100644
--- a/qa/qa/runtime/namespace.rb
+++ b/qa/qa/runtime/namespace.rb
@@ -25,7 +25,7 @@ module QA
end
def sandbox_name
- Runtime::Env.sandbox_name || 'gitlab-qa-sandbox-group'
+ @sandbox_name ||= Runtime::Env.sandbox_name || "gitlab-qa-sandbox-group-#{Time.now.wday + 1}"
end
end
end
diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb
index 85c06e6c307..53980b8e051 100644
--- a/qa/qa/service/docker_run/base.rb
+++ b/qa/qa/service/docker_run/base.rb
@@ -12,9 +12,7 @@ module QA
end
def logs
- shell "docker logs #{@name}" do |line|
- yield " #{line.chomp}"
- end
+ shell "docker logs #{@name}"
end
def network
diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb
index 0a8ac39dabd..1584b577af1 100644
--- a/qa/qa/service/docker_run/gitlab_runner.rb
+++ b/qa/qa/service/docker_run/gitlab_runner.rb
@@ -26,13 +26,13 @@ module QA
end
def config
- @config ||= <<~END
+ @config ||= <<~CONFIG
concurrent = 1
check_interval = 0
[session_server]
session_timeout = 1800
- END
+ CONFIG
end
def register!
@@ -40,15 +40,14 @@ module QA
docker run -d --rm --network #{runner_network} --name #{@name}
#{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker}
--privileged
- #{@image} #{add_gitlab_tls_cert if @address.include? "https"} && docker exec --detach #{@name} sh -c "#{register_command}"
+ #{@image} #{add_gitlab_tls_cert if @address.include? 'https'}
+ && docker exec --detach #{@name} sh -c "#{register_command}"
CMD
wait_until_running_and_configured
# Prove airgappedness
- if runner_network == 'airgapped'
- shell("docker exec #{@name} sh -c '#{prove_airgap}'")
- end
+ shell("docker exec #{@name} sh -c '#{prove_airgap}'") if runner_network == 'airgapped'
end
def tags=(tags)
@@ -66,7 +65,7 @@ module QA
args << "--registration-token #{@token}"
args << if run_untagged
- raise CONFLICTING_VARIABLES_MESSAGE % [:tags=, :run_untagged, run_untagged] if @tags&.any?
+ raise format(CONFLICTING_VARIABLES_MESSAGE, :tags=, :run_untagged, run_untagged) if @tags&.any?
'--run-untagged=true'
else
@@ -86,7 +85,7 @@ module QA
end
<<~CMD.strip
- printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml &&
+ printf '#{config.chomp.gsub(/\n/, '\\n').gsub('"', '\"')}' > /etc/gitlab-runner/config.toml &&
gitlab-runner register \
#{args.join(' ')} &&
gitlab-runner run
diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb
index 1215268919c..8563c3656a8 100644
--- a/qa/qa/service/praefect_manager.rb
+++ b/qa/qa/service/praefect_manager.rb
@@ -115,7 +115,7 @@ module QA
def node_state(name)
state = "stopped"
- wait_until_shell_command("docker inspect -f {{.State.Status}} #{name}") do |line|
+ wait_until_shell_command("docker inspect -f {{.State.Status}} #{name}", stream_progress: false) do |line|
QA::Runtime::Logger.debug(line)
break state = "running" if line.include?("running")
break state = "paused" if line.include?("paused")
@@ -164,7 +164,8 @@ module QA
end
def query_read_distribution
- output = shell "docker exec #{@gitlab} bash -c 'curl -s http://localhost:9090/api/v1/query?query=gitaly_praefect_read_distribution'" do |line|
+ cmd = "docker exec #{@gitlab} bash -c 'curl -s http://localhost:9090/api/v1/query?query=gitaly_praefect_read_distribution'"
+ output = shell(cmd, stream_progress: false) do |line|
QA::Runtime::Logger.debug(line)
break line
end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 33d1d10b515..376e4f74845 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -6,34 +6,42 @@ module QA
module Service
module Shellout
using Rainbow
+
CommandError = Class.new(StandardError)
module_function
- ##
- # TODO, make it possible to use generic QA framework classes
- # as a library - gitlab-org/gitlab-qa#94
- #
- def shell(command, stdin_data: nil, fail_on_exception: true)
- QA::Runtime::Logger.info("Executing `#{command}`".cyan)
+ def shell(command, stdin_data: nil, fail_on_exception: true, stream_progress: true, mask_secrets: []) # rubocop:disable Metrics/CyclomaticComplexity
+ cmd_string = Array(command).join(' ')
+
+ QA::Runtime::Logger.info("Executing: `#{cmd_string.cyan}`")
Open3.popen2e(*command) do |stdin, out, wait|
stdin.puts(stdin_data) if stdin_data
stdin.close if stdin_data
+
+ print_progress_dots = stream_progress && !Runtime::Env.running_in_ci?
cmd_output = ''
- if block_given?
- out.each do |line|
- cmd_output += line
- yield line
- end
+ out.each do |line|
+ line = mask_secrets_on_string(line, mask_secrets)
+
+ cmd_output += line
+ yield line if block_given?
+
+ # indicate progress for local run by printing dots
+ print "." if print_progress_dots
end
- out.each_char { |char| print char }
+ # add newline after progress dots
+ puts if print_progress_dots && !cmd_output.empty?
if wait.value.exited? && wait.value.exitstatus.nonzero? && fail_on_exception
- raise CommandError, "Command failed: #{command} \nCommand Output: #{cmd_output}"
+ Runtime::Logger.error("Command output:\n#{cmd_output.strip}") unless cmd_output.empty?
+ raise CommandError, "Command: `#{mask_secrets_on_string(cmd_string, mask_secrets)}` failed! ✘"
end
+
+ Runtime::Logger.debug("Command output:\n#{cmd_output.strip}") unless cmd_output.empty?
end
end
@@ -46,21 +54,24 @@ module QA
def wait_until_shell_command(cmd, **kwargs)
sleep_interval = kwargs.delete(:sleep_interval) || 1
+ stream_progress = kwargs.delete(:stream_progress).then { |arg| arg.nil? ? true : false }
Support::Waiter.wait_until(sleep_interval: sleep_interval, **kwargs) do
- shell cmd do |line|
+ shell(cmd, stream_progress: stream_progress) do |line|
break true if yield line
end
end
end
def wait_until_shell_command_matches(cmd, regex, **kwargs)
- wait_until_shell_command(cmd, **kwargs) do |line|
- QA::Runtime::Logger.debug(line.chomp)
-
+ wait_until_shell_command(cmd, stream_progress: false, **kwargs) do |line|
line =~ regex
end
end
+
+ def mask_secrets_on_string(str, secrets)
+ secrets.reduce(str) { |s, secret| s.gsub(secret, '****') }
+ end
end
end
end
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
index edb7838e81d..89150c73069 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -73,7 +73,7 @@ module QA
let(:source_commits) { source_project.commits(auto_paginate: true).map { |c| c[:id] } }
let(:source_labels) { source_project.labels(auto_paginate: true).map { |l| l.except(:id) } }
let(:source_milestones) { source_project.milestones(auto_paginate: true).map { |ms| ms.except(:id, :web_url, :project_id) } }
- let(:source_pipelines) { source_project.pipelines.map { |pp| pp.except(:id, :web_url, :project_id) } }
+ let(:source_pipelines) { source_project.pipelines(auto_paginate: true).map { |pp| pp.except(:id, :web_url, :project_id) } }
let(:source_mrs) { fetch_mrs(source_project, source_api_client) }
let(:source_issues) { fetch_issues(source_project, source_api_client) }
@@ -259,7 +259,7 @@ module QA
missing_comments = verify_comments(type, actual, expected)
{
- "#{type}s": (expected.keys - actual.keys).map { |it| actual[it].slice(:title, :url) },
+ "#{type}s": (expected.keys - actual.keys).map { |it| actual[it]&.slice(:title, :url) }.compact,
"#{type}_comments": missing_comments
}
end
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb
index 201b8efdf6a..6910b6a7fa2 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_release_spec.rb
@@ -29,7 +29,7 @@ module QA
# @param [Hash] release
# @return [Hash]
def comparable_release(release)
- release&.except(:_links, :evidences)&.merge(
+ release&.except(:_links)&.merge(
{
author: release[:author].except(:web_url),
commit: release[:commit].except(:web_url),
@@ -42,12 +42,14 @@ module QA
}),
milestones: release[:milestones].map do |milestone|
milestone.except(:id, :project_id).merge({ web_url: milestone[:web_url].split("/-/").last })
- end
- # TODO: Add back evidence testing once implemented
- # https://gitlab.com/gitlab-org/gitlab/-/issues/360567
- # evidences: release[:evidences].map do |evidence|
- # evidence.merge({ filepath: evidence[:filepath].split("/-/").last })
- # end
+ end,
+ # evidences are not directly migrated but rather recreated on the same releases,
+ # so we only check the json file is there
+ evidences: release[:evidences].map do |evidence|
+ evidence
+ .except(:collected_at, :sha)
+ .merge({ filepath: evidence[:filepath].split("/-/").last.gsub(/\d+\.json/, "*.json") })
+ end
}
)
end
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 33ec24d1be1..539da92f471 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
@@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
- describe 'Project access token' do
+ describe 'Project access token', :reliable do
before(:all) do
@project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! do |pat|
pat.project = Resource::ReusableProject.fabricate_via_api!
@@ -11,7 +11,7 @@ module QA
@user_api_client = Runtime::API::Client.new(:gitlab, personal_access_token: @project_access_token.token)
end
- context 'for the same project', :reliable do
+ context 'for the same project' do
it 'can be used to create a file via the project API', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347858' do
expect do
Resource::File.fabricate_via_api! do |file|
diff --git a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
new file mode 100644
index 00000000000..faef321c89d
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
@@ -0,0 +1,176 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'User', :requires_admin do
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+
+ let!(:sub_group) do
+ QA::Resource::Group.fabricate_via_api! do |group|
+ group.path = "sub-group-to-test-user-access-#{SecureRandom.hex(8)}"
+ end
+ end
+
+ context 'when added to parent group' do
+ let!(:parent_group_user) do
+ Resource::User.fabricate_via_api! do |user|
+ user.api_client = admin_api_client
+ end
+ end
+
+ let!(:parent_group_user_api_client) do
+ Runtime::API::Client.new(:gitlab, user: parent_group_user)
+ end
+
+ let!(:sub_group_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = sub_group
+ project.name = "sub-group-project-to-test-user-access"
+ project.initialize_with_readme = true
+ end
+ end
+
+ before do
+ sub_group.sandbox.add_member(parent_group_user)
+ end
+
+ it(
+ 'is allowed to push code to sub-group project via the CLI',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363345'
+ ) do
+ expect do
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = sub_group_project.repository_http_location.uri
+ push.file_name = 'test.txt'
+ push.file_content = "# This is a test project named #{sub_group_project.name}"
+ push.commit_message = 'Add test.txt'
+ push.branch_name = "new_branch_#{SecureRandom.hex(8)}"
+ push.user = parent_group_user
+ end
+ end.not_to raise_error
+ end
+
+ it(
+ 'is allowed to create a file in sub-group project via the API',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363348'
+ ) do
+ expect do
+ Resource::File.fabricate_via_api! do |file|
+ file.api_client = parent_group_user_api_client
+ file.project = sub_group_project
+ file.branch = "new_branch_#{SecureRandom.hex(8)}"
+ file.commit_message = 'Add new file'
+ file.name = 'test.txt'
+ file.content = "New file"
+ end
+ end.not_to raise_error
+ end
+
+ it(
+ 'is allowed to commit to sub-group project via the API',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349'
+ ) do
+ expect do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.api_client = parent_group_user_api_client
+ commit.project = sub_group_project
+ commit.branch = "new_branch_#{SecureRandom.hex(8)}"
+ commit.start_branch = sub_group_project.default_branch
+ commit.commit_message = 'Add new file'
+ commit.add_files([
+ { file_path: 'test.txt', content: 'new file' }
+ ])
+ end
+ end.not_to raise_error
+ end
+
+ after do
+ parent_group_user.remove_via_api!
+ sub_group_project.remove_via_api!
+ sub_group.remove_via_api!
+ end
+ end
+
+ context 'when added to sub-group' do
+ let!(:parent_group_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = sub_group.sandbox
+ project.name = "sub-group-project-to-test-user-access"
+ project.initialize_with_readme = true
+ end
+ end
+
+ let!(:sub_group_user) do
+ Resource::User.fabricate_via_api! do |user|
+ user.api_client = admin_api_client
+ end
+ end
+
+ let!(:sub_group_user_api_client) do
+ Runtime::API::Client.new(:gitlab, user: sub_group_user)
+ end
+
+ before do
+ sub_group.add_member(sub_group_user)
+ end
+
+ it(
+ 'is not allowed to push code to parent group project via the CLI',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363344'
+ ) do
+ expect do
+ Resource::Repository::Push.fabricate! do |push|
+ push.repository_http_uri = parent_group_project.repository_http_location.uri
+ push.file_name = 'test.txt'
+ push.file_content = "# This is a test project named #{parent_group_project.name}"
+ push.commit_message = 'Add test.txt'
+ push.branch_name = "new_branch_#{SecureRandom.hex(8)}"
+ push.user = sub_group_user
+ end
+ end.to raise_error(QA::Support::Run::CommandError, /You are not allowed to push code to this project/)
+ end
+
+ it(
+ 'is not allowed to create a file in parent group project via the API',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363343'
+ ) do
+ expect do
+ Resource::File.fabricate_via_api! do |file|
+ file.api_client = sub_group_user_api_client
+ file.project = parent_group_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
+
+ it(
+ 'is not allowed to commit to parent group project via the API',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363342'
+ ) do
+ expect do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.api_client = sub_group_user_api_client
+ commit.project = parent_group_project
+ commit.branch = "new_branch_#{SecureRandom.hex(8)}"
+ commit.start_branch = parent_group_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
+
+ after do
+ sub_group_user.remove_via_api!
+ parent_group_project.remove_via_api!
+ sub_group.remove_via_api!
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb
index 2b48945137c..8ca0ae1f052 100644
--- a/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb
+++ b/qa/qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb
@@ -1,107 +1,59 @@
# frozen_string_literal: true
module QA
- # TODO:
- # Remove FF :ci_trigger_forward_variables
- # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed
- RSpec.describe 'Verify', :runner, feature_flag: {
- name: 'ci_trigger_forward_variables',
- scope: :global
- } do
+ RSpec.describe 'Verify', :runner do
describe 'Pipeline API defined variable inheritance' do
include_context 'variable inheritance test prep'
before do
add_ci_file(downstream1_project, [downstream1_ci_file])
add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file, upstream_child2_ci_file])
-
- start_pipeline_via_api_with_variable
-
- Support::Waiter.wait_until(max_duration: 180, sleep_interval: 5) do
- upstream_pipeline.status == 'success'
- end
-
- Support::Waiter.wait_until(max_duration: 180, sleep_interval: 5) do
- downstream1_pipeline.pipeline_variables && child1_pipeline.pipeline_variables
- end
end
it(
'is determined based on forward:pipeline_variables condition',
:aggregate_failures,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360745',
- quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361400', type: :investigating }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360745'
) do
- # Is inheritable when true
- expect(child1_pipeline).to have_variable(key: key, value: value),
- "Expected to find `{key: 'TEST_VAR', value: 'This is great!'}`" \
- " but got #{child1_pipeline.pipeline_variables}"
-
- # Is not inheritable when false
- expect(child2_pipeline).not_to have_variable(key: key, value: value),
- "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \
- " but got #{child2_pipeline.pipeline_variables}"
-
- # Is not inheritable by default
- expect(downstream1_pipeline).not_to have_variable(key: key, value: value),
- "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \
- " but got #{downstream1_pipeline.pipeline_variables}"
- end
+ start_pipeline_via_api_with_variable
+ wait_for_pipelines
- def start_pipeline_via_api_with_variable
- Resource::Pipeline.fabricate_via_api! do |pipeline|
- pipeline.project = upstream_project
- pipeline.variables = [{ key: key, value: value, variable_type: 'env_var' }]
- end
+ # When forward:pipeline_variables is true
+ expect_downstream_pipeline_to_inherit_variable
- Support::Waiter.wait_until { upstream_project.pipelines.size > 1 }
- end
+ # When forward:pipeline_variables is false
+ expect_downstream_pipeline_not_to_inherit_variable(upstream_project, 'child2_trigger')
- def upstream_pipeline
- Resource::Pipeline.fabricate_via_api! do |pipeline|
- pipeline.project = upstream_project
- pipeline.id = upstream_project.pipelines.first[:id]
- end
+ # When forward:pipeline_variables is default
+ expect_downstream_pipeline_not_to_inherit_variable(downstream1_project, 'downstream1_trigger')
end
- def child1_pipeline
- Resource::Pipeline.fabricate_via_api! do |pipeline|
- pipeline.project = upstream_project
- pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'child1_trigger')
+ def start_pipeline_via_api_with_variable
+ # Wait for 1st pipeline to finish
+ Support::Waiter.wait_until do
+ upstream_project.pipelines.size == 1 && upstream_pipeline.status == 'success'
end
- end
- def child2_pipeline
Resource::Pipeline.fabricate_via_api! do |pipeline|
pipeline.project = upstream_project
- pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'child2_trigger')
+ pipeline.variables = [{ key: key, value: value, variable_type: 'env_var' }]
end
- end
- def downstream1_pipeline
- Resource::Pipeline.fabricate_via_api! do |pipeline|
- pipeline.project = downstream1_project
- pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: 'downstream1_trigger')
- end
+ # Wait for this pipeline to be created
+ Support::Waiter.wait_until { upstream_project.pipelines.size > 1 }
end
def upstream_ci_file
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
- stages:
- - test
- - deploy
-
child1_trigger:
- stage: test
trigger:
include: .child1-ci.yml
forward:
pipeline_variables: true
child2_trigger:
- stage: test
trigger:
include: .child2-ci.yml
forward:
@@ -109,12 +61,25 @@ module QA
# default behavior
downstream1_trigger:
- stage: deploy
trigger:
project: #{downstream1_project.full_path}
YAML
}
end
+
+ def expect_downstream_pipeline_to_inherit_variable
+ pipeline = downstream_pipeline(upstream_project, 'child1_trigger')
+ expect(pipeline).to have_variable(key: key, value: value),
+ "Expected to find `{key: 'TEST_VAR', value: 'This is great!'}`" \
+ " but got #{pipeline.pipeline_variables}"
+ end
+
+ def expect_downstream_pipeline_not_to_inherit_variable(project, bridge_name)
+ pipeline = downstream_pipeline(project, bridge_name)
+ expect(pipeline).not_to have_variable(key: key, value: value),
+ "Did not expect to find `{key: 'TEST_VAR', value: 'This is great!'}`" \
+ " but got #{pipeline.pipeline_variables}"
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/14_non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb
index 829d52b8e93..10076a329bc 100644
--- a/qa/qa/specs/features/browser_ui/14_non_devops/performance_bar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_product_intelligence/performance_bar_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Non-devops' do
+ RSpec.describe 'Product Intelligence' do
describe 'Performance bar display', :requires_admin, :skip_live_env do
context 'when logged in as an admin user' do
# performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided)
@@ -20,7 +20,10 @@ module QA
end
end
- it 'shows results for the original request and AJAX requests', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348030' do
+ it(
+ 'shows results for the original request and AJAX requests',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348030'
+ ) do
# Issue pages always make AJAX requests
Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = 'Performance bar test'
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_product_intelligence/service_ping_default_enabled_spec.rb
index 72090306fd9..cc2888063ca 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_product_intelligence/service_ping_default_enabled_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Non-devops' do
+ RSpec.describe 'Product Intelligence' do
describe 'Service ping default enabled' do
context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
before do
@@ -11,7 +11,10 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end
- it 'has service ping toggle enabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335' 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_product_intelligence/service_ping_disabled_spec.rb
index 791bd688cea..f762adf52a1 100644
--- a/qa/qa/specs/features/browser_ui/14_non_devops/service_ping_disabled_spec.rb
+++ b/qa/qa/specs/features/browser_ui/14_product_intelligence/service_ping_disabled_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Non-devops' do
+ RSpec.describe 'Product Intelligence' do
describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do
context 'when disabled from gitlab.yml config' do
before do
@@ -11,7 +11,10 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end
- it 'has service ping toggle is disabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340' 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/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index 7b60adae836..db02d1e8390 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Project transfer between groups', :reliable do
let(:source_group) do
Resource::Group.fabricate_via_api! do |group|
- group.path = 'source-group'
+ group.path = "source-group-#{SecureRandom.hex(8)}"
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
index 2f9ceeb98eb..44cae31f5d8 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb
@@ -1,14 +1,10 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do
+ RSpec.describe 'Manage' do
describe 'Check for broken images', :requires_admin do
before(:context) do
- admin = QA::Resource::User.init do |user|
- user.username = QA::Runtime::User.admin_username
- user.password = QA::Runtime::User.admin_password
- end
- @api_client = Runtime::API::Client.new(:gitlab, user: admin)
+ @api_client = Runtime::API::Client.as_admin
@new_user = Resource::User.fabricate_via_api! do |user|
user.api_client = @api_client
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
index 5d0befea1ce..fb486ab1532 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/personal_project_permissions_spec.rb
@@ -73,25 +73,21 @@ module QA
private
def expect_owner_permissions_allow_delete_issue
- expect do
- issue.visit!
+ issue.visit!
- Page::Project::Issue::Show.perform(&:delete_issue)
+ Page::Project::Issue::Show.perform(&:delete_issue)
- Page::Project::Issue::Index.perform do |index|
- expect(index).not_to have_issue(issue)
- end
- end.not_to raise_error
+ Page::Project::Issue::Index.perform do |index|
+ expect(index).not_to have_issue(issue)
+ end
end
def expect_maintainer_permissions_do_not_allow_delete_issue
- expect do
- issue.visit!
+ issue.visit!
- Page::Project::Issue::Show.perform do |issue|
- expect(issue).not_to have_delete_issue_button
- end
- end.not_to raise_error
+ Page::Project::Issue::Show.perform do |issue|
+ expect(issue).not_to have_delete_issue_button
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
index 381a25a14d0..63ae90aed9c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/project_access_token_spec.rb
@@ -5,7 +5,10 @@ module QA
describe 'Project access tokens', :reliable do
let(:project_access_token) { QA::Resource::ProjectAccessToken.fabricate_via_browser_ui! }
- it 'can be created and revoked via the UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347688' do
+ it(
+ 'can be created and revoked via the UI',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347688'
+ ) do
expect(project_access_token.token).not_to be_nil
project_access_token.revoke_via_ui!
diff --git a/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb
new file mode 100644
index 00000000000..8de9d7c2049
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/user/user_inherited_access_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Manage' do
+ describe 'User', :requires_admin do
+ let(:admin_api_client) { Runtime::API::Client.as_admin }
+
+ let!(:sub_group) do
+ QA::Resource::Group.fabricate_via_api! do |group|
+ group.path = "sub-group-to-test-user-access-#{SecureRandom.hex(8)}"
+ end
+ end
+
+ context 'when added to parent group' do
+ let!(:parent_group_user) do
+ Resource::User.fabricate_via_api! do |user|
+ user.api_client = admin_api_client
+ end
+ end
+
+ let!(:parent_group_user_api_client) do
+ Runtime::API::Client.new(:gitlab, user: parent_group_user)
+ end
+
+ let!(:sub_group_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = sub_group
+ project.name = "sub-group-project-to-test-user-access"
+ project.initialize_with_readme = true
+ end
+ end
+
+ before do
+ sub_group.sandbox.add_member(parent_group_user)
+ end
+
+ it(
+ 'is allowed to edit the sub-group project files',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363467'
+ ) do
+ Flow::Login.sign_in(as: parent_group_user)
+ sub_group_project.visit!
+
+ Page::Project::Show.perform do |project|
+ project.click_file('README.md')
+ end
+
+ Page::File::Show.perform(&:click_edit)
+
+ Page::File::Form.perform do |file_form|
+ expect(file_form).to have_element(:commit_button)
+ end
+ end
+
+ after do
+ parent_group_user.remove_via_api!
+ sub_group_project.remove_via_api!
+ sub_group.remove_via_api!
+ end
+ end
+
+ context 'when added to sub-group' do
+ let!(:parent_group_project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.group = sub_group.sandbox
+ project.name = "sub-group-project-to-test-user-access"
+ project.initialize_with_readme = true
+ end
+ end
+
+ let!(:sub_group_user) do
+ Resource::User.fabricate_via_api! do |user|
+ user.api_client = admin_api_client
+ end
+ end
+
+ let!(:sub_group_user_api_client) do
+ Runtime::API::Client.new(:gitlab, user: sub_group_user)
+ end
+
+ before do
+ sub_group.add_member(sub_group_user)
+ end
+
+ it(
+ 'is not allowed to edit the parent group project files',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363466'
+ ) do
+ Flow::Login.sign_in(as: sub_group_user)
+ parent_group_project.visit!
+
+ Page::Project::Show.perform do |project|
+ project.click_file('README.md')
+ end
+
+ Page::File::Show.perform(&:click_edit)
+
+ expect(page).to have_text("You can’t edit files directly in this project.")
+ end
+
+ after do
+ sub_group_user.remove_via_api!
+ parent_group_project.remove_via_api!
+ sub_group.remove_via_api!
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
index 0d706aef6ab..f41e5985622 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan', :orchestrated, :smtp do
+ RSpec.describe 'Plan', :orchestrated, :smtp, :reliable do
describe 'Email Notification' do
include Support::API
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 84e1332cc8a..b8f1824126d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -4,7 +4,6 @@ module QA
RSpec.describe(
'Plan',
:smoke,
- feature_flag: { name: 'vue_issues_list', scope: :group },
quarantine: { issue: 'https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7099', type: :investigating, only: { subdomain: 'pre' } }
) do
describe 'Issue creation' do
@@ -12,8 +11,6 @@ module QA
let(:closed_issue) { Resource::Issue.fabricate_via_api! { |issue| issue.project = project } }
before do
- Runtime::Feature.enable(:vue_issues_list, group: project.group)
-
Flow::Login.sign_in
end
@@ -26,9 +23,6 @@ module QA
Page::Project::Menu.perform(&:click_issues)
- # TODO: Remove this method when the `Runtime::Feature.enable` method call is removed
- Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff)
-
Page::Project::Issue::Index.perform do |index|
expect(index).to have_issue(issue)
end
@@ -49,9 +43,6 @@ module QA
Page::Project::Menu.perform(&:click_issues)
- # TODO: Remove this method when the `Runtime::Feature.enable` method call is removed
- Page::Project::Issue::Index.perform(&:wait_for_vue_issues_list_ff)
-
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(closed_issue)
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
index 044cc118085..36b7378ee2a 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Custom issue templates' do
let(:template_name) { 'custom_issue_template'}
let(:template_content) { 'This is a custom issue template test' }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index c9536699cc5..d8fa7480f01 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'filter issue comments activities' do
before do
Flow::Login.sign_in
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb
index b7fa57a3270..d8435407296 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/jira_issue_import_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan' do
+ RSpec.describe 'Plan', :reliable do
describe 'Jira issue import', :jira, :orchestrated, :requires_admin do
let(:jira_project_key) { "JITD" }
let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" }
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
index ac0f16b50cc..9c90cf6ae3d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan', :requires_admin, :actioncable, :orchestrated, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/293699', type: :bug } do
+ RSpec.describe 'Plan', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/293699', type: :bug } do
describe 'Assignees' do
let(:user1) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:user2) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
index b544c9aa211..d198d79c5fe 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb
@@ -2,11 +2,7 @@
module QA
RSpec.describe 'Create' do
- describe 'Merge request creation from fork', quarantine: {
- only: :production,
- issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/343801",
- type: :investigating
- } do
+ describe 'Merge request creation from fork' do
let(:merge_request) do
Resource::MergeRequestFromFork.fabricate_via_browser_ui! do |merge_request|
merge_request.fork_branch = 'feature-branch'
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 85270791f0f..ac53357a86f 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
@@ -4,6 +4,8 @@ module QA
RSpec.describe 'Create', :runner do
describe 'Merge requests' do
shared_examples 'merge when pipeline succeeds' do |repeat: 1|
+ let(:runner_name) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'merge-when-pipeline-succeeds'
@@ -14,24 +16,12 @@ module QA
let!(:runner) do
Resource::Runner.fabricate! do |runner|
runner.project = project
- runner.name = "runner-for-#{project.name}"
- runner.tags = ["runner-for-#{project.name}"]
+ runner.name = runner_name
+ runner.tags = [runner_name]
end
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
+ let!(:ci_file) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
@@ -39,60 +29,46 @@ module QA
[
{
file_path: '.gitlab-ci.yml',
- content: <<~EOF
+ content: <<~YAML
test:
- tags: ["runner-for-#{project.name}"]
- script: sleep 30
+ tags: ["#{runner_name}"]
+ script: sleep 15
only:
- merge_requests
- EOF
+ YAML
}
]
)
end
+ end
- repeat.times do |i|
- QA::Runtime::Logger.info("Transient bug test - Trial #{i}") if transient_test
+ before do
+ Flow::Login.sign_in
+ end
- branch_name = "mr-test-#{SecureRandom.hex(6)}-#{i}"
+ after do
+ runner&.remove_via_api!
+ end
- # Create a branch that will be merged into the default branch
- Resource::Repository::ProjectPush.fabricate! do |project_push|
- project_push.project = project
- project_push.new_branch = true
- project_push.branch_name = branch_name
- project_push.file_name = "#{branch_name}.txt"
- end
+ it 'merges after pipeline succeeds' do
+ transient_test = repeat > 1
+
+ repeat.times do |i|
+ QA::Runtime::Logger.info("Transient bug test - Trial #{i + 1}") if transient_test
- # Create a merge request to merge the branch we just created
+ # Create a merge request to trigger pipeline
merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
- merge_request.source_branch = branch_name
- merge_request.no_preparation = true
+ merge_request.description = Faker::Lorem.sentence
+ merge_request.target_new_branch = false
+ merge_request.source_branch = "mr-test-#{SecureRandom.hex(6)}-#{i + 1}"
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.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
@@ -102,8 +78,6 @@ module QA
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')
@@ -115,7 +89,7 @@ module QA
end
aggregate_failures do
- expect { mr.merged? }.to eventually_be_truthy.within(max_duration: 60), "Expected content 'The changes were merged' but it did not appear."
+ expect { mr.merged? }.to eventually_be_truthy.within(max_duration: 120), "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')
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index 536abfa5303..2280cc971a7 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -28,15 +28,19 @@ module QA
merge_request.visit!
- Page::MergeRequest::Show.perform do |merge_request|
- expect(merge_request).to have_content('Merge blocked: the source branch must be rebased onto the target branch.')
- expect(merge_request).to be_fast_forward_not_possible
- expect(merge_request).not_to have_merge_button
+ Page::MergeRequest::Show.perform do |mr_page|
+ expect(mr_page).to have_content('Merge blocked: the source branch must be rebased onto the target branch.')
+ expect(mr_page).to be_fast_forward_not_possible
+ expect(mr_page).not_to have_merge_button
+ expect(merge_request.project.commits.size).to eq(2)
- merge_request.rebase!
+ mr_page.rebase!
- expect(merge_request).to have_merge_button
- expect(merge_request).to be_fast_forward_possible
+ expect { mr_page.has_merge_button? }.to eventually_be_truthy.within(max_duration: 60, reload_page: mr_page)
+
+ mr_page.merge!
+
+ expect(merge_request.project.commits.size).to eq(3)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb
index 6caa8e64d56..eb6449181b5 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb
@@ -21,7 +21,7 @@ module QA
file.project = project
file.commit_message = 'Add new file'
file.name = "test-folder/#{file_name}"
- file.content = "### Heading\n\n[Gitlab link](https://gitlab.com/)"
+ file.content = "### Heading\n\n[Example link](https://example.com/)"
end
project.visit!
@@ -35,9 +35,9 @@ module QA
aggregate_failures 'markdown file contents' do
expect(show).to have_content('Heading')
- expect(show).to have_content('Gitlab link')
+ expect(show).to have_content('Example link')
expect(show).not_to have_content('###')
- expect(show).not_to have_content('https://gitlab.com/')
+ expect(show).not_to have_content('https://example.com/')
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 a50b995e483..e4204776c46 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
@@ -35,18 +35,18 @@ module QA
let(:files) do
[
- {
- number: 1,
- content: first_file_content
- },
- {
- number: 2,
- content: second_file_content
- },
- {
- number: 3,
- content: third_file_content
- }
+ {
+ number: 1,
+ content: first_file_content
+ },
+ {
+ number: 2,
+ content: second_file_content
+ },
+ {
+ number: 3,
+ content: third_file_content
+ }
]
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
index b93bc1545d1..0f01a965e7b 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb
@@ -9,7 +9,7 @@ module QA
snippet.description = ' '
snippet.visibility = 'Private'
snippet.file_name = 'markdown_file.md'
- snippet.file_content = "### Snippet heading\n\n[Gitlab link](https://gitlab.com/)"
+ snippet.file_content = "### Snippet heading\n\n[Example link](https://example.com/)"
end
end
@@ -30,9 +30,9 @@ module QA
expect(snippet).to have_visibility_type(/private/i)
expect(snippet).to have_file_name('markdown_file.md')
expect(snippet).to have_file_content('Snippet heading')
- expect(snippet).to have_file_content('Gitlab link')
+ expect(snippet).to have_file_content('Example link')
expect(snippet).not_to have_file_content('###')
- expect(snippet).not_to have_file_content('https://gitlab.com/')
+ expect(snippet).not_to have_file_content('https://example.com/')
end
end
end
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 e9871a70560..046327f780b 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
@@ -3,9 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Open a fork in Web IDE',
- # TODO: remove limitation to only run on main when the test is fixed
- only: { pipeline: :main },
- quarantine: {
+ skip: {
issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/351696",
type: :flaky
} do
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb
index bd200e57ff9..7782c0240e9 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb
@@ -1,13 +1,7 @@
# frozen_string_literal: true
module QA
- # TODO:
- # Remove FF :ci_trigger_forward_variables
- # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed
- RSpec.describe 'Verify', :runner, feature_flag: {
- name: 'ci_trigger_forward_variables',
- scope: :global
- } do
+ RSpec.describe 'Verify', :runner do
describe 'UI defined variable' do
include_context 'variable inheritance test prep'
@@ -16,16 +10,13 @@ module QA
add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file])
start_pipeline_with_variable
- Page::Project::Pipeline::Show.perform do |show|
- Support::Waiter.wait_until { show.passed? }
- end
+ wait_for_pipelines
end
it(
'is inheritable when forward:pipeline_variables is true',
:aggregate_failures,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358197',
- quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361338', type: :investigating }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358197'
) do
visit_job_page('child1', 'child1_job')
verify_job_log_shows_variable_value
@@ -40,19 +31,13 @@ module QA
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
- stages:
- - test
- - deploy
-
child1_trigger:
- stage: test
trigger:
include: .child1-ci.yml
forward:
pipeline_variables: true
downstream1_trigger:
- stage: deploy
trigger:
project: #{downstream1_project.full_path}
forward:
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb
index 2bd0be542fe..69a99483b38 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_non_inheritable_when_forward_pipeline_variables_false_spec.rb
@@ -1,13 +1,7 @@
# frozen_string_literal: true
module QA
- # TODO:
- # Remove FF :ci_trigger_forward_variables
- # when https://gitlab.com/gitlab-org/gitlab/-/issues/355572 is closed
- RSpec.describe 'Verify', :runner, feature_flag: {
- name: 'ci_trigger_forward_variables',
- scope: :global
- } do
+ RSpec.describe 'Verify', :runner do
describe 'UI defined variable' do
include_context 'variable inheritance test prep'
@@ -17,16 +11,13 @@ module QA
add_ci_file(upstream_project, [upstream_ci_file, upstream_child1_ci_file, upstream_child2_ci_file])
start_pipeline_with_variable
- Page::Project::Pipeline::Show.perform do |show|
- Support::Waiter.wait_until { show.passed? }
- end
+ wait_for_pipelines
end
it(
'is not inheritable when forward:pipeline_variables is false',
:aggregate_failures,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358199',
- quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361339', type: :investigating }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358199'
) do
visit_job_page('child1', 'child1_job')
verify_job_log_does_not_show_variable_value
@@ -40,8 +31,7 @@ module QA
it(
'is not inheritable by default',
:aggregate_failures,
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358200',
- quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361339', type: :investigating }
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358200'
) do
visit_job_page('child2', 'child2_job')
verify_job_log_does_not_show_variable_value
@@ -56,12 +46,7 @@ module QA
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
- stages:
- - test
- - deploy
-
child1_trigger:
- stage: test
trigger:
include: .child1-ci.yml
forward:
@@ -69,12 +54,10 @@ module QA
# default behavior
child2_trigger:
- stage: test
trigger:
include: .child2-ci.yml
downstream1_trigger:
- stage: deploy
trigger:
project: #{downstream1_project.full_path}
forward:
@@ -82,7 +65,6 @@ module QA
# default behavior
downstream2_trigger:
- stage: deploy
trigger:
project: #{downstream2_project.full_path}
YAML
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb
deleted file mode 100644
index c5408f30d16..00000000000
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/multi-project_pipelines_spec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Verify' do
- describe 'Multi-project pipelines' do
- let(:downstream_job_name) { 'downstream_job' }
- let(:executor) { "qa-runner-#{SecureRandom.hex(4)}" }
- let!(:group) { Resource::Group.fabricate_via_api! }
-
- let(:upstream_project) do
- Resource::Project.fabricate_via_api! do |project|
- project.group = group
- project.name = 'upstream-project'
- end
- end
-
- let(:downstream_project) do
- Resource::Project.fabricate_via_api! do |project|
- project.group = group
- project.name = 'downstream-project'
- end
- end
-
- let!(:runner) do
- Resource::Runner.fabricate_via_api! do |runner|
- runner.token = group.reload!.runners_token
- runner.name = executor
- runner.tags = [executor]
- end
- end
-
- before do
- add_ci_file(downstream_project, downstream_ci_file)
- add_ci_file(upstream_project, upstream_ci_file)
-
- Flow::Login.sign_in
- upstream_project.visit!
- Flow::Pipeline.visit_latest_pipeline(status: 'passed')
- end
-
- after do
- runner.remove_via_api!
- [upstream_project, downstream_project].each(&:remove_via_api!)
- end
-
- it(
- 'creates a multi-project pipeline with artifact download',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358064'
- ) do
- Page::Project::Pipeline::Show.perform do |show|
- expect(show).to have_passed
- expect(show).not_to have_job(downstream_job_name)
-
- show.expand_linked_pipeline
-
- expect(show).to have_job(downstream_job_name)
- end
- end
-
- private
-
- def add_ci_file(project, file)
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.project = project
- commit.commit_message = 'Add CI config file'
- commit.add_files([file])
- end
- end
-
- def upstream_ci_file
- {
- file_path: '.gitlab-ci.yml',
- content: <<~YAML
- stages:
- - test
- - deploy
-
- job1:
- stage: test
- tags: ["#{executor}"]
- script: echo 'done' > output.txt
- artifacts:
- paths:
- - output.txt
-
- staging:
- stage: deploy
- trigger:
- project: #{downstream_project.path_with_namespace}
- strategy: depend
- YAML
- }
- end
-
- def downstream_ci_file
- {
- file_path: '.gitlab-ci.yml',
- content: <<~YAML
- "#{downstream_job_name}":
- stage: test
- tags: ["#{executor}"]
- needs:
- - project: #{upstream_project.path_with_namespace}
- job: job1
- ref: main
- artifacts: true
- script: cat output.txt
- YAML
- }
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
deleted file mode 100644
index f2278c7bf6d..00000000000
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_dependent_relationship_spec.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- RSpec.describe 'Verify', :runner, :reliable do
- describe 'Parent-child pipelines dependent relationship' do
- let!(:project) do
- Resource::Project.fabricate_via_api! do |project|
- project.name = 'pipelines-dependent-relationship'
- end
- end
-
- let!(:runner) do
- Resource::Runner.fabricate_via_api! do |runner|
- runner.project = project
- runner.name = project.name
- runner.tags = ["#{project.name}"]
- end
- end
-
- before do
- Flow::Login.sign_in
- end
-
- after do
- runner.remove_via_api!
- end
-
- it(
- 'parent pipelines passes if child passes',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358062'
- ) do
- add_ci_files(success_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline
-
- Page::Project::Pipeline::Show.perform do |parent_pipeline|
- expect(parent_pipeline).to have_child_pipeline
- expect { parent_pipeline.has_passed? }.to eventually_be_truthy
- end
- end
-
- it(
- 'parent pipeline fails if child fails',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358063'
- ) do
- add_ci_files(fail_child_ci_file)
- Flow::Pipeline.visit_latest_pipeline
-
- Page::Project::Pipeline::Show.perform do |parent_pipeline|
- expect(parent_pipeline).to have_child_pipeline
- expect { parent_pipeline.has_failed? }.to eventually_be_truthy
- end
- end
-
- private
-
- def success_child_ci_file
- {
- file_path: '.child-ci.yml',
- content: <<~YAML
- child_job:
- stage: test
- tags: ["#{project.name}"]
- needs:
- - project: #{project.path_with_namespace}
- job: job1
- ref: main
- artifacts: true
- script:
- - cat output.txt
- - echo "Child job done!"
-
- YAML
- }
- end
-
- def fail_child_ci_file
- {
- file_path: '.child-ci.yml',
- content: <<~YAML
- child_job:
- stage: test
- tags: ["#{project.name}"]
- script: exit 1
-
- YAML
- }
- end
-
- def parent_ci_file
- {
- file_path: '.gitlab-ci.yml',
- content: <<~YAML
- stages:
- - build
- - test
- - deploy
-
- default:
- tags: ["#{project.name}"]
-
- job1:
- stage: build
- script: echo "build success" > output.txt
- artifacts:
- paths:
- - output.txt
-
- job2:
- stage: test
- trigger:
- include: ".child-ci.yml"
- strategy: depend
-
- job3:
- stage: deploy
- script: echo "parent deploy done"
-
- YAML
- }
- end
-
- def add_ci_files(child_ci_file)
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.project = project
- commit.commit_message = 'Add parent and child pipelines CI files.'
- commit.add_files(
- [
- child_ci_file,
- parent_ci_file
- ]
- )
- end.project.visit!
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
index d03ebd5aba3..205b4d1168a 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb
@@ -31,7 +31,7 @@ module QA
project.remove_via_api!
end
- it 'creates 2 trigger jobs and passes corresponding matrix variables', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348000', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/361346', type: :investigating } do
+ it 'creates 2 trigger jobs and passes corresponding matrix variables', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348000' do
Page::Project::Pipeline::Show.perform do |parent_pipeline|
trigger_title1 = 'deploy: [ovh, monitoring]'
trigger_title2 = 'deploy: [ovh, app]'
diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb
new file mode 100644
index 00000000000..adcf91a550c
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module QA
+ # Spark various endpoints (git, web, api, sidekiq) to ensure
+ # GitLab-QA covers these various endpoints. The `api_json.log` can then be consumed after test run.
+ #
+ # User sets a CI variable via UI (Web write) ->
+ # Git push (Git read/write) ->
+ # pipeline created (Sidekiq read/write) ->
+ # runner picks up pipeline (API read/write) ->
+ # User views pipeline succeeds (Web read)
+ RSpec.describe 'Verify', :runner do
+ context 'Endpoint Coverage' do
+ let!(:project) do
+ Resource::Project.fabricate_via_api! do |project|
+ project.name = 'endpoint-coverage'
+ end
+ end
+
+ let!(:runner) do
+ Resource::Runner.fabricate_via_api! do |runner|
+ runner.project = project
+ runner.name = project.name
+ runner.tags = [project.name]
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+ project.visit!
+ end
+
+ after do
+ project.remove_via_api!
+ runner.remove_via_api!
+ end
+
+ it(
+ 'spans r/w postgres web sidekiq git api',
+ testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/360837'
+ ) do
+ # create a CI variable via UI
+ Page::Project::Show.perform(&:go_to_ci_cd_settings)
+
+ Page::Project::Settings::CiCd.perform do |ci_cd|
+ ci_cd.expand_ci_variables do |vars|
+ vars.click_add_variable
+ vars.fill_variable('CI_VARIABLE', 'secret-value')
+ end
+ end
+
+ # push a .gitlab-ci.yml file that exposes artifacts
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = '.gitlab-ci.yml'
+ push.file_content = <<~YAML
+ test:
+ tags:
+ - #{project.name}
+ script:
+ - mkdir out; echo $CI_VARIABLE > out/file.out
+ artifacts:
+ paths:
+ - out/
+ expire_in: 1h
+ YAML
+ push.commit_message = 'Commit .gitlab-ci.yml'
+ end
+
+ # observe pipeline creation
+ project.visit!
+ Flow::Pipeline.visit_latest_pipeline
+
+ Page::Project::Pipeline::Show.perform do |show|
+ show.click_job('test')
+ end
+
+ Page::Project::Job::Show.perform do |show|
+ # user views job succeeding
+ expect { show.passed? }.to eventually_be_truthy.within(max_duration: 60, sleep_interval: 1)
+
+ show.click_browse_button
+ end
+
+ Page::Project::Artifact::Show.perform do |show|
+ show.go_to_directory('out')
+ expect(show).to have_content('file.out')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
index f570ad335fe..dacfc6c801b 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_omnibus_spec.rb
@@ -233,10 +233,13 @@ module QA
expect(registry).to have_registry_repository(project.path_with_namespace)
registry.click_on_image(project.path_with_namespace)
+
expect(registry).to have_tag('master')
registry.click_delete
- expect(registry).not_to have_tag('master')
+
+ expect { registry.has_no_tag?('master') }
+ .to eventually_be_truthy.within(max_duration: 60, reload_page: page)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
index 1df68cc729d..27b11d697cc 100644
--- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb
@@ -83,7 +83,8 @@ module QA
expect(registry).to have_tag('master')
registry.click_delete
- expect(registry).not_to have_tag('master')
+ expect { registry.has_no_tag?('master') }
+ .to eventually_be_truthy.within(max_duration: 60, reload_page: page)
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 e37102c17f7..677b8970a75 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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
+ RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable do
describe 'Generic Repository' do
include Runtime::Fixtures
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 61a92daf129..124e7743728 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
@@ -40,7 +40,6 @@ module QA
let!(:another_project) do
Resource::Project.fabricate_via_api! do |another_project|
another_project.name = 'npm-instance-level-install'
- another_project.template_name = 'express'
another_project.group = project.group
end
end
diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
index ab6896ca26f..e70b95db1a5 100644
--- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
+++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget/nuget_project_level_spec.rb
@@ -1,10 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Package', :orchestrated, :packages, :object_storage, :reliable, quarantine: {
- type: :flaky,
- issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/361704"
- } do
+ RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
describe 'NuGet project level endpoint' do
include Support::Helpers::MaskToken
@@ -16,13 +13,7 @@ module QA
end
end
- let(:personal_access_token) do
- unless Page::Main::Menu.perform(&:signed_in?)
- Flow::Login.sign_in
- end
-
- Resource::PersonalAccessToken.fabricate!
- end
+ let(:personal_access_token) { Resource::PersonalAccessToken.fabricate! }
let(:project_deploy_token) do
Resource::ProjectDeployToken.fabricate_via_api! do |deploy_token|
@@ -113,19 +104,34 @@ module QA
{
file_path: '.gitlab-ci.yml',
content: <<~YAML
- deploy-and-install:
- image: mcr.microsoft.com/dotnet/sdk:5.0
- 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
- - "dotnet add dotnetcore.csproj package #{package.name} --version 1.0.0"
- rules:
- - if: '$CI_COMMIT_BRANCH == "#{project.default_branch}"'
- tags:
- - "runner-for-#{project.name}"
+ stages:
+ - deploy
+ - install
+
+ deploy:
+ stage: deploy
+ image: mcr.microsoft.com/dotnet/sdk:5.0
+ 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.name}"
+
+ install:
+ stage: install
+ image: mcr.microsoft.com/dotnet/sdk:5.0
+ script:
+ - 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 add dotnetcore.csproj package #{package.name} --version 1.0.0"
+ rules:
+ - if: '$CI_COMMIT_BRANCH == "#{project.default_branch}"'
+ tags:
+ - "runner-for-#{project.name}"
YAML
},
{
@@ -150,7 +156,17 @@ module QA
Flow::Pipeline.visit_latest_pipeline
Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.click_job('deploy-and-install')
+ pipeline.click_job('deploy')
+ end
+
+ Page::Project::Job::Show.perform do |job|
+ expect(job).to be_successful(timeout: 800)
+ end
+
+ page.go_back
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ pipeline.click_job('install')
end
Page::Project::Job::Show.perform do |job|
diff --git a/qa/qa/specs/features/sanity/feature_flags_spec.rb b/qa/qa/specs/features/sanity/feature_flags_spec.rb
new file mode 100644
index 00000000000..7e68c70ee09
--- /dev/null
+++ b/qa/qa/specs/features/sanity/feature_flags_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Feature flag handler sanity checks', :sanity_feature_flags do
+ context 'with an existing feature flag definition file' do
+ let(:definition) do
+ path = Pathname.new('../config/feature_flags')
+ .expand_path(Runtime::Path.qa_root)
+ .glob('**/*.yml')
+ .first
+ YAML.safe_load(File.read(path))
+ end
+
+ it 'reads the correct default enabled state' do
+ # This test will fail if we ever remove all the feature flags, but that's very unlikely given how many there
+ # are and how much we rely on them.
+ expect(QA::Runtime::Feature.enabled?(definition['name'])).to be definition['default_enabled']
+ end
+ end
+
+ describe 'feature flag definition files' do
+ let(:file) do
+ path = Pathname.new('../config/feature_flags/development').expand_path(Runtime::Path.qa_root)
+ Tempfile.new(%w[ff-test .yml], path)
+ end
+
+ let(:flag) { Pathname.new(file.path).basename('.yml').to_s }
+
+ before do
+ definition = <<~YAML
+ name: #{flag}
+ type: development
+ default_enabled: #{flag_enabled}
+ YAML
+ File.write(file, definition)
+ end
+
+ after do
+ file.close!
+ end
+
+ context 'with a default disabled feature flag' do
+ let(:flag_enabled) { 'false' }
+
+ it 'reads the flag as disabled' do
+ expect(QA::Runtime::Feature.enabled?(flag)).to be false
+ end
+
+ it 'reads as enabled after the flag is enabled' do
+ QA::Runtime::Feature.enable(flag)
+
+ expect { QA::Runtime::Feature.enabled?(flag) }.to eventually_be_truthy
+ end
+ end
+
+ context 'with a default enabled feature flag' do
+ let(:flag_enabled) { 'true' }
+
+ it 'reads the flag as enabled' do
+ expect(QA::Runtime::Feature.enabled?(flag)).to be true
+ end
+
+ it 'reads as disabled after the flag is disabled' do
+ QA::Runtime::Feature.disable(flag)
+
+ expect { QA::Runtime::Feature.enabled?(flag) }.to eventually_be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb b/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb
index fbe517f51f8..45caeced35c 100644
--- a/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb
+++ b/qa/qa/specs/features/shared_contexts/variable_inheritance_shared_context.rb
@@ -45,13 +45,11 @@ module QA
end
before do
- Runtime::Feature.enable(:ci_trigger_forward_variables)
Flow::Login.sign_in
end
after do
runner.remove_via_api!
- Runtime::Feature.disable(:ci_trigger_forward_variables)
end
def start_pipeline_with_variable
@@ -64,6 +62,13 @@ module QA
end
end
+ def wait_for_pipelines
+ Support::Waiter.wait_until(max_duration: 300, sleep_interval: 10) do
+ upstream_pipeline.status == 'success' &&
+ downstream_pipeline(downstream1_project, 'downstream1_trigger').status == 'success'
+ end
+ end
+
def add_ci_file(project, files)
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
@@ -93,6 +98,20 @@ module QA
end
end
+ def upstream_pipeline
+ Resource::Pipeline.fabricate_via_api! do |pipeline|
+ pipeline.project = upstream_project
+ pipeline.id = upstream_project.pipelines.first[:id]
+ end
+ end
+
+ def downstream_pipeline(project, bridge_name)
+ Resource::Pipeline.fabricate_via_api! do |pipeline|
+ pipeline.project = project
+ pipeline.id = upstream_pipeline.downstream_pipeline_id(bridge_name: bridge_name)
+ end
+ end
+
def upstream_child1_ci_file
{
file_path: '.child1-ci.yml',
diff --git a/qa/qa/specs/helpers/feature_flag.rb b/qa/qa/specs/helpers/feature_flag.rb
index b9de2332c19..7e618f19ed5 100644
--- a/qa/qa/specs/helpers/feature_flag.rb
+++ b/qa/qa/specs/helpers/feature_flag.rb
@@ -23,11 +23,17 @@ module QA
else
# Tests using a feature flag scoped to an actor (ex: :project, :user, :group), or
# with no scope defined (such as in the case of a low risk global feature flag),
- # will only be skipped in canary and production due to no admin account existing there.
- example.metadata[:skip] = feature_flag_message if ContextSelector.context_matches?(:production)
+ # will only be skipped on environments without an admin account
+ example.metadata[:skip] = feature_flag_message if skip_env_for_scoped_feature_flag
end
end
end
+
+ private
+
+ def skip_env_for_scoped_feature_flag
+ ContextSelector.context_matches?(:production) || ContextSelector.context_matches?({ subdomain: :pre })
+ end
end
end
end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index 68b624b3f2e..801b9b222a4 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -12,6 +12,7 @@ module QA
DEFAULT_TEST_PATH_ARGS = ['--', File.expand_path('./features', __dir__)].freeze
DEFAULT_STD_ARGS = [$stderr, $stdout].freeze
+ DEFAULT_SKIPPED_TAGS = %w[orchestrated transient sanity_feature_flags].freeze
def initialize
@tty = false
@@ -25,7 +26,7 @@ module QA
if tags.any?
tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) }
else
- tags_for_rspec.push(%w[--tag ~orchestrated --tag ~transient]) unless (%w[-t --tag] & options).any?
+ tags_for_rspec.push(DEFAULT_SKIPPED_TAGS.map { |tag| %W[--tag ~#{tag}] }) unless (%w[-t --tag] & options).any?
end
tags_for_rspec.push(%w[--tag ~geo]) unless QA::Runtime::Env.geo_environment?
diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb
index b130fff0488..e1c08515521 100644
--- a/qa/qa/specs/spec_helper.rb
+++ b/qa/qa/specs/spec_helper.rb
@@ -15,6 +15,9 @@ QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes)
Dir[::File.join(__dir__, "features/shared_examples/*.rb")].sort.each { |f| require f }
Dir[::File.join(__dir__, "features/shared_contexts/*.rb")].sort.each { |f| require f }
+# For JH additionally process when `jh/` exists
+require_relative('../../../jh/qa/qa/specs/spec_helper') if GitlabEdition.jh?
+
RSpec.configure do |config|
config.include QA::Support::Matchers::EventuallyMatcher
config.include QA::Support::Matchers::HaveMatcher
@@ -35,6 +38,8 @@ RSpec.configure do |config|
QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}")
QA::Runtime::Example.current = example
+ visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.remote_mobile_device_name
+
# Reset fabrication counters tracked in resource base
Thread.current[:api_fabrication] = 0
Thread.current[:browser_ui_fabrication] = 0
@@ -45,6 +50,15 @@ RSpec.configure do |config|
QA::Git::Repository.new.delete_netrc
end
+ config.prepend_after do |example|
+ if example.exception
+ page = Capybara.page
+
+ QA::Support::PageErrorChecker.log_request_errors(page)
+ QA::Support::PageErrorChecker.check_page_for_error_code(page)
+ end
+ end
+
# Add fabrication time to spec metadata
config.append_after do |example|
example.metadata[:api_fabrication] = Thread.current[:api_fabrication]
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 0c0a1a90ff2..a1bbe9f378a 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -141,31 +141,18 @@ module QA
get(url).tap { |resp| not_ok_error.call(resp) if resp.code != HTTP_STATUS_OK }
end
- page, pages = response.headers.values_at(:x_page, :x_total_pages)
+ page, pages, next_page = response.headers.values_at(:x_page, :x_total_pages, :x_next_page)
api_endpoint = url.match(%r{v4/(\S+)\?})[1]
QA::Runtime::Logger.debug("Fetching page (#{page}/#{pages}) for '#{api_endpoint}' ...") unless pages.to_i <= 1
yield parse_body(response)
- next_link = pagination_links(response).find { |link| link[:rel] == 'next' }
- break unless next_link
+ break if next_page.empty?
- url = next_link[:url]
+ url = url.match?(/&page=\d+/) ? url.gsub(/&page=\d+/, "&page=#{next_page}") : "#{url}&page=#{next_page}"
end
end
-
- def pagination_links(response)
- link = response.headers[:link]
- return unless link
-
- link.split(',').map do |link|
- match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/)
- break nil unless match
-
- { url: match[:url], rel: match[:rel] }
- end.compact
- end
end
end
end
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index 998802fe8b7..8114e838ede 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -13,6 +13,10 @@ module QA
def_delegators :new, :configure!, :move_regenerated_report, :download_report, :upload_report
+ def initialize(report_name = nil)
+ @report_name = report_name
+ end
+
# Configure knapsack report
#
# * Setup variables
@@ -31,8 +35,8 @@ module QA
#
# @return [void]
def download_report
- logger.debug("Downloading latest knapsack report for '#{report_name}' to '#{report_path}'")
- return logger.debug("Report already exists, skipping!") if File.exist?(report_path)
+ logger.info("Downloading latest knapsack report for '#{report_name}' to '#{report_path}'")
+ return logger.info("Report already exists, skipping!") if File.exist?(report_path)
file = client.get_object(BUCKET, report_file)
File.write(report_path, file[:body])
diff --git a/qa/qa/support/matchers/eventually_matcher.rb b/qa/qa/support/matchers/eventually_matcher.rb
index 2fb5249d9af..01d07585f57 100644
--- a/qa/qa/support/matchers/eventually_matcher.rb
+++ b/qa/qa/support/matchers/eventually_matcher.rb
@@ -53,7 +53,7 @@ module QA
def wait_and_check(actual, expectation_name)
attempt = 0
- QA::Runtime::Logger.debug(
+ QA::Runtime::Logger.info(
"Running eventually matcher with '#{operator_msg}' operator with: '#{retry_args}' arguments"
)
QA::Support::Retrier.retry_until(**retry_args, log: false) do
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index bc5ee645965..6dfb348a347 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -13,15 +13,15 @@ module QA
end
def refresh(skip_finished_loading_check: false)
- log("refreshing #{current_url}")
+ log("refreshing #{current_url}", :info)
super
end
def scroll_to(selector, text: nil)
- msg = "scrolling to :#{Rainbow(selector).underline.bright}"
+ msg = "scrolling to :#{highlight_element(selector)}"
msg += " with text: #{text}" if text
- log(msg)
+ log(msg, :info)
super
end
@@ -39,7 +39,7 @@ module QA
element = super
- log("found :#{Rainbow(name).underline.bright}")
+ log("found :#{name}")
element
end
@@ -49,41 +49,41 @@ module QA
elements = super
- log("found #{elements.size} :#{Rainbow(name).underline.bright}") if elements
+ log("found #{elements.size} :#{name}") if elements
elements
end
def check_element(name, click_by_js = nil)
- log("checking :#{name}")
+ log("checking :#{highlight_element(name)}", :info)
super
end
def uncheck_element(name, click_by_js = nil)
- log("unchecking :#{name}")
+ log("unchecking :#{highlight_element(name)}", :info)
super
end
def click_element_coordinates(name, **kwargs)
- log(%Q(clicking the coordinates of :#{name}))
+ log(%(clicking the coordinates of :#{highlight_element(name)}), :info)
super
end
def click_element(name, page = nil, **kwargs)
- msg = ["clicking :#{Rainbow(name).underline.bright}"]
+ msg = ["clicking :#{highlight_element(name)}"]
msg << ", expecting to be at #{page.class}" if page
- msg << "with args #{kwargs}"
- log(msg.compact.join(' '))
+ log(msg.join(' '), :info)
+ log("with args #{kwargs}")
super
end
def click_via_capybara(method, locator)
- log("clicking via capybara using '#{method}(#{locator})'")
+ log("clicking via capybara using '#{method}(#{locator})'", :info)
super
end
@@ -91,13 +91,13 @@ module QA
def fill_element(name, content)
masked_content = name.to_s.match?(/token|key|password/) ? '*****' : content
- log(%Q(filling :#{name} with "#{masked_content}"))
+ log(%(filling :#{highlight_element(name)} with "#{masked_content}"), :info)
super
end
def select_element(name, value)
- log(%Q(selecting "#{value}" in :#{name}))
+ log(%(selecting "#{value}" in :#{highlight_element(name)}), :info)
super
end
@@ -121,7 +121,7 @@ module QA
def has_text?(text, **kwargs)
found = super
- log(%Q{has_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}})
+ log(%(has_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}))
found
end
@@ -129,7 +129,7 @@ module QA
def has_no_text?(text, **kwargs)
found = super
- log(%Q{has_no_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}})
+ log(%(has_no_text?('#{text}', wait: #{kwargs[:wait] || Capybara.default_max_wait_time}) returned #{found}))
found
end
@@ -173,13 +173,26 @@ module QA
private
- def log(msg)
- QA::Runtime::Logger.debug(msg)
+ # Log message
+ #
+ # @param [String] msg
+ # @param [Symbol] level
+ # @return [void]
+ def log(msg, level = :debug)
+ QA::Runtime::Logger.public_send(level, msg)
+ end
+
+ # Highlight element for enhanced logging
+ #
+ # @param [String] element
+ # @return [String]
+ def highlight_element(element)
+ element.to_s.underline.bright
end
def log_has_element_or_not(method, name, found, **kwargs)
- msg = ["#{method} :#{Rainbow(name).underline.bright}"]
- msg << %Q(with text "#{kwargs[:text]}") if kwargs[:text]
+ msg = ["#{method} :#{name}"]
+ msg << %(with text "#{kwargs[:text]}") if kwargs[:text]
msg << "class: #{kwargs[:class]}" if kwargs[:class]
msg << "(wait: #{kwargs[:wait] || Capybara.default_max_wait_time})"
msg << "returned: #{found}"
diff --git a/qa/qa/support/page_error_checker.rb b/qa/qa/support/page_error_checker.rb
index 192b8c147cd..acba25643ae 100644
--- a/qa/qa/support/page_error_checker.rb
+++ b/qa/qa/support/page_error_checker.rb
@@ -3,14 +3,14 @@
module QA
module Support
class PageErrorChecker
+ PageError = Class.new(StandardError)
+
class << self
def report!(page, error_code)
request_id_string = ''
if error_code == 500
request_id = parse_five_c_page_request_id(page)
- if request_id
- request_id_string = "\n\n" + Loglinking.failure_metadata(request_id)
- end
+ request_id_string = "\n\n#{Loglinking.failure_metadata(request_id)}" if request_id
end
report = if QA::Runtime::Env.browser == :chrome
@@ -19,14 +19,17 @@ module QA
status_code_report(error_code)
end
- raise "Error Code #{error_code}\n\n"\
- "#{report}\n\n"\
- "Path: #{page.current_path}"\
- "#{request_id_string}"
+ raise(PageError, <<~MSG)
+ Error Code: #{error_code}
+
+ #{report}
+
+ Path: #{page.current_path}#{request_id_string}
+ MSG
end
def parse_five_c_page_request_id(page)
- Nokogiri::HTML.parse(page.html).xpath("/html/body/div/p[1]/code").children.first
+ page_html(page).xpath("/html/body/div/p[1]/code").children.first
end
def return_chrome_errors(page, error_code)
@@ -43,23 +46,28 @@ module QA
"Status code #{error_code} found"
end
+ # rubocop:disable Rails/Pluck
def check_page_for_error_code(page)
- error_code = 0
+ QA::Runtime::Logger.debug "Performing page error check!"
+
# Test for 404 img alt
- error_code = 404 if Nokogiri::HTML.parse(page.html).xpath("//img").map { |t| t[:alt] }.first.eql?('404')
+ return report!(page, 404) if page_html(page).xpath("//img").map { |t| t[:alt] }.first.eql?('404')
# 500 error page in header surrounded by newlines, try to match
- five_hundred_test = Nokogiri::HTML.parse(page.html).xpath("//h1/text()").map.first
- unless five_hundred_test.nil?
- error_code = 500 if five_hundred_test.text.include?('500')
+ five_hundred_test = page_html(page).xpath("//h1/text()").map.first
+ five_hundred_title = page_html(page).xpath("//head/title/text()").map.first
+ if five_hundred_test&.text&.include?('500') && five_hundred_title&.text.eql?('Something went wrong (500)')
+ return report!(page, 500)
end
+
# GDK shows backtrace rather than error page
- error_code = 500 if Nokogiri::HTML.parse(page.html).xpath("//body//section").map { |t| t[:class] }.first.eql?('backtrace')
+ report!(page, 500) if page_html(page).xpath("//body//section").map { |t| t[:class] }.first.eql?('backtrace')
+ rescue StandardError => e
+ raise e if e.is_a?(PageError)
- unless error_code == 0
- report!(page, error_code)
- end
+ QA::Runtime::Logger.error("Page error check raised error `#{e.class}`: #{e.message}")
end
+ # rubocop:enable Rails/Pluck
# Log request errors triggered from async api calls from the browser
#
@@ -67,7 +75,7 @@ module QA
# using QA::Runtime::Logger
# @param [Capybara::Session] page
def log_request_errors(page)
- return if QA::Runtime::Browser.blank_page?
+ return if !QA::Runtime::Env.can_intercept? || QA::Runtime::Browser.blank_page?
url = page.driver.browser.current_url
QA::Runtime::Logger.debug "Fetching API error cache for #{url}"
@@ -84,9 +92,7 @@ module QA
"#{error_metadata} -- #{request_id_string}"
end
- unless errors.nil? || errors.empty?
- QA::Runtime::Logger.error "Interceptor Api Errors\n#{errors.join("\n")}"
- end
+ QA::Runtime::Logger.error "Interceptor Api Errors\n#{errors.join("\n")}" unless errors.nil? || errors.empty?
# clear the cache after logging the errors
page.execute_script <<~JS
@@ -106,6 +112,10 @@ module QA
private
+ def page_html(page)
+ Nokogiri::HTML.parse(page.html)
+ end
+
def group_errors(errors)
errors.each_with_object({}) do |error, memo|
url = error['url']&.split('?')&.first || 'Unknown url'
diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb
index 89674a1d5c6..d863ed0491d 100644
--- a/qa/qa/support/wait_for_requests.rb
+++ b/qa/qa/support/wait_for_requests.rb
@@ -8,22 +8,20 @@ module QA
DEFAULT_MAX_WAIT_TIME = 60
def wait_for_requests(skip_finished_loading_check: false, skip_resp_code_check: false)
- # We have tests that use 404 pages, allow them to skip this check
- unless skip_resp_code_check
- QA::Support::PageErrorChecker.check_page_for_error_code(Capybara.page)
- end
-
Waiter.wait_until(log: false) do
finished_all_ajax_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true)
end
- QA::Support::PageErrorChecker.log_request_errors(Capybara.page) if QA::Runtime::Env.can_intercept?
rescue Repeater::WaitExceededError
raise $!, 'Page did not fully load. This could be due to an unending async request or loading icon.'
end
def finished_all_ajax_requests?
requests = %w[window.pendingRequests window.pendingRailsUJSRequests 0]
- requests.unshift('(window.Interceptor && window.Interceptor.activeFetchRequests)') if Runtime::Env.can_intercept?
+
+ if Runtime::Env.can_intercept?
+ requests.unshift('(window.Interceptor && window.Interceptor.activeFetchRequests)')
+ end
+
script = requests.join(' || ')
Capybara.page.evaluate_script(script).zero? # rubocop:disable Style/NumericPredicate
end
diff --git a/qa/qa/tools/delete_projects.rb b/qa/qa/tools/delete_projects.rb
index 1f550f035d1..96ea5f8de7e 100644
--- a/qa/qa/tools/delete_projects.rb
+++ b/qa/qa/tools/delete_projects.rb
@@ -39,14 +39,18 @@ module QA
def delete_projects(project_ids)
$stdout.puts "Deleting #{project_ids.length} projects..."
project_ids.each do |project_id|
- delete_response = delete Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url
- dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
+ request_url = Runtime::API::Request.new(@api_client, "/projects/#{project_id}").url
+ path = parse_body(get(request_url))[:path_with_namespace]
+ $stdout.puts "\nDeleting project #{path}..."
+
+ delete_response = delete(request_url)
+ dot_or_f = delete_response.code.between?(200, 300) ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m"
print dot_or_f
end
end
def fetch_group_id
- group_name = ENV['TOP_LEVEL_GROUP_NAME'] || 'gitlab-qa-sandbox-group'
+ group_name = ENV['TOP_LEVEL_GROUP_NAME'] || "gitlab-qa-sandbox-group-#{Time.now.wday + 1}"
group_search_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_name}").url
JSON.parse(group_search_response.body)["id"]
end
diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb
index 11b45365d4c..355bd6bf10d 100644
--- a/qa/qa/tools/delete_subgroups.rb
+++ b/qa/qa/tools/delete_subgroups.rb
@@ -32,14 +32,18 @@ module QA
def delete_subgroups(sub_group_ids)
$stdout.puts "Deleting #{sub_group_ids.length} subgroups..."
sub_group_ids.each do |subgroup_id|
- delete_response = delete Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url
- dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
+ request_url = Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url
+ path = parse_body(get(request_url))[:full_path]
+ $stdout.puts "\nDeleting subgroup #{path}..."
+
+ delete_response = delete(request_url)
+ dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF - #{delete_response}\e[0m"
print dot_or_f
end
end
def fetch_group_id
- group_name = ENV['TOP_LEVEL_GROUP_NAME'] || 'gitlab-qa-sandbox-group'
+ group_name = ENV['TOP_LEVEL_GROUP_NAME'] || "gitlab-qa-sandbox-group-#{Time.now.wday + 1}"
group_search_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_name}" ).url
JSON.parse(group_search_response.body)["id"]
end
diff --git a/qa/qa/tools/delete_test_snippets.rb b/qa/qa/tools/delete_test_snippets.rb
index 5da962b14f3..e590077f81c 100644
--- a/qa/qa/tools/delete_test_snippets.rb
+++ b/qa/qa/tools/delete_test_snippets.rb
@@ -12,7 +12,7 @@ module QA
class DeleteTestSnippets
include Support::API
- ITEMS_PER_PAGE = '100'
+ ITEMS_PER_PAGE = '1'
def initialize(delete_before: (Date.today - 1).to_s, dry_run: false)
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
@@ -69,6 +69,11 @@ module QA
to_delete
end
snippet_ids.concat(snippets.map { |snippet| snippet['id'] })
+
+ if (page_no + 1) == 1000
+ puts "Stopping at page 1000 to avoid timeout, total number of pages: #{pages}"
+ break
+ end
end
snippet_ids.uniq
diff --git a/qa/qa/tools/test_resources_handler.rb b/qa/qa/tools/test_resources_handler.rb
index 5218e6df217..f968fb8b26c 100644
--- a/qa/qa/tools/test_resources_handler.rb
+++ b/qa/qa/tools/test_resources_handler.rb
@@ -61,7 +61,10 @@ module QA
#
# E.g: staging/failed-test-resources-<randomhex>.json
def upload(ci_project_name)
- return puts "\nNothing to upload!" if files.empty?
+ if files.empty?
+ puts "\nNothing to upload!"
+ exit 0
+ end
files.each do |file|
file_name = "#{ci_project_name}/#{file.split('/').last}"
@@ -81,7 +84,10 @@ module QA
arr << obj.name
end
- return puts "\nNothing to download!" if files_list.blank?
+ if files_list.blank?
+ puts "\nNothing to download!"
+ exit 0
+ end
FileUtils.mkdir_p('tmp/')
@@ -103,10 +109,19 @@ module QA
def files
Runtime::Logger.info('Gathering JSON files...')
files = Dir.glob(@file_pattern)
- abort("There is no file with this pattern #{@file_pattern}") if files.empty?
+
+ if files.empty?
+ puts "There is no file with this pattern #{@file_pattern}"
+ exit 0
+ end
files.reject! { |file| File.zero?(file) }
+ if files.empty?
+ puts "\nAll files were empty and rejected, nothing more to do!"
+ exit 0
+ end
+
files
end
@@ -122,7 +137,7 @@ module QA
transformed_values = resources.transform_values! do |v|
v.reject do |attributes|
- attributes['info'] == "with full_path 'gitlab-qa-sandbox-group'" ||
+ attributes['info']&.match(/with full_path 'gitlab-qa-sandbox-group(-\d)?'/) ||
attributes['http_method'] == 'get' && !attributes['info']&.include?("with username 'qa-") ||
attributes['api_path'] == 'Cannot find resource API path'
end
@@ -132,7 +147,10 @@ module QA
end
def delete_resources(resources)
- Runtime::Logger.info('Nothing to delete.') && return if resources.nil?
+ if resources.nil?
+ puts "\nNo resources left to delete after filtering!"
+ exit 0
+ end
resources.each_with_object([]) do |(key, value), failures|
value.each do |resource|
@@ -144,7 +162,7 @@ module QA
if delete_response.code == 202 || delete_response.code == 204
Runtime::Logger.info("Deleting #{resource_info}... SUCCESS")
else
- Runtime::Logger.info("Deleting #{resource_info}... FAILED")
+ Runtime::Logger.info("Deleting #{resource_info}... FAILED - #{delete_response}")
failures << resource_info
end
end
@@ -160,7 +178,10 @@ module QA
abort("\nPlease provide GITLAB_ADDRESS") unless ENV['GITLAB_ADDRESS']
abort("\nPlease provide GITLAB_QA_ACCESS_TOKEN") unless ENV['GITLAB_QA_ACCESS_TOKEN']
- @api_client ||= Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
+ @api_client ||= Runtime::API::Client.new(
+ ENV['GITLAB_ADDRESS'],
+ personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']
+ )
end
def gcs_storage
@@ -175,7 +196,9 @@ module QA
# Path to GCS service account json key file
# Or the content of the key file as a hash
def json_key
- abort("\nPlease provide QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS") unless ENV['QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS']
+ unless ENV['QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS']
+ abort("\nPlease provide QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS")
+ end
@json_key ||= ENV["QA_FAILED_TEST_RESOURCES_GCS_CREDENTIALS"]
end
diff --git a/qa/spec/git/repository_spec.rb b/qa/spec/git/repository_spec.rb
index a6a49f5907a..1b88cba2ba5 100644
--- a/qa/spec/git/repository_spec.rb
+++ b/qa/spec/git/repository_spec.rb
@@ -4,6 +4,7 @@ RSpec.describe QA::Git::Repository do
include QA::Support::Helpers::StubEnv
shared_context 'unresolvable git directory' do
+ let(:logger) { instance_double(Logger, info: nil, debug: nil) }
let(:repo_uri) { 'http://foo/bar.git' }
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
let(:env_vars) { [%q{HOME="temp"}] }
@@ -22,6 +23,7 @@ RSpec.describe QA::Git::Repository do
before do
stub_env('GITLAB_USERNAME', 'root')
allow(repository).to receive(:tmp_home_dir).and_return(tmp_netrc_dir)
+ allow(QA::Runtime::Logger).to receive(:logger).and_return(logger)
end
around do |example|
diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb
index 6dac8e0e3ee..f23f4aa728b 100644
--- a/qa/spec/resource/base_spec.rb
+++ b/qa/spec/resource/base_spec.rb
@@ -112,18 +112,16 @@ RSpec.describe QA::Resource::Base do
let(:method) { 'api' }
before do
- allow(QA::Runtime::Logger).to receive(:debug)
+ allow(QA::Runtime::Logger).to receive(:info)
allow(resource).to receive(:api_support?).and_return(true)
allow(resource).to receive(:fabricate_via_api!)
allow(resource).to receive(:api_client) { api_client }
end
it 'logs the resource and build method' do
- stub_env('QA_DEBUG', 'true')
-
subject.fabricate_via_api!('something', resource: resource, parents: [])
- expect(QA::Runtime::Logger).to have_received(:debug) do |&msg|
+ expect(QA::Runtime::Logger).to have_received(:info) do |&msg|
expect(msg.call).to match_regex(log_regex)
end
end
@@ -155,15 +153,13 @@ RSpec.describe QA::Resource::Base do
let(:method) { 'browser_ui' }
before do
- allow(QA::Runtime::Logger).to receive(:debug)
+ allow(QA::Runtime::Logger).to receive(:info)
end
it 'logs the resource and build method' do
- stub_env('QA_DEBUG', 'true')
-
subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
- expect(QA::Runtime::Logger).to have_received(:debug) do |&msg|
+ expect(QA::Runtime::Logger).to have_received(:info) do |&msg|
expect(msg.call).to match_regex(log_regex)
end
end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index 22603497019..a41d7385c41 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -47,13 +47,6 @@ RSpec.describe QA::Runtime::Env do
default: false
end
- describe '.debug?' do
- it_behaves_like 'boolean method',
- method: :debug?,
- env_key: 'QA_DEBUG',
- default: false
- end
-
describe '.webdriver_headless?' do
it_behaves_like 'boolean method',
method: :webdriver_headless?,
@@ -264,20 +257,6 @@ RSpec.describe QA::Runtime::Env do
end
end
- describe '.log_destination' do
- it 'returns $stdout if QA_LOG_PATH is not defined' do
- stub_env('QA_LOG_PATH', nil)
-
- expect(described_class.log_destination).to eq($stdout)
- end
-
- it 'returns the path if QA_LOG_PATH is defined' do
- stub_env('QA_LOG_PATH', 'path/to_file')
-
- expect(described_class.log_destination).to eq('path/to_file')
- end
- end
-
describe '.can_test?' do
it_behaves_like 'boolean method with parameter',
method: :can_test?,
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
index 88f5cd5be93..72ba915d99b 100644
--- a/qa/spec/runtime/feature_spec.rb
+++ b/qa/spec/runtime/feature_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe QA::Runtime::Feature do
.to receive(:get)
.and_return(Struct.new(:code, :body).new(200, %Q([{ "name": "a_flag", "state": "conditional", "gates": #{gates} }])))
- expect(described_class.enabled?(feature_flag, scope => actor)).to be_truthy
+ expect(described_class.enabled?(feature_flag, scope => actor)).to be true
end
end
end
@@ -172,7 +172,7 @@ RSpec.describe QA::Runtime::Feature do
.to receive(:get)
.and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
- expect(described_class.enabled?(feature_flag)).to be_truthy
+ expect(described_class.enabled?(feature_flag)).to be true
end
it 'raises an error when the scope is unknown' do
@@ -224,6 +224,75 @@ RSpec.describe QA::Runtime::Feature do
let(:gates) { %q([{"key": "groups", "value": ["foo"]}]) }
end
end
+
+ context 'when a feature flag is not found via the API and there is no definition file' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[]'))
+ allow(Dir).to receive(:glob).and_return([])
+ end
+
+ it 'raises an error' do
+ expect { described_class.enabled?(feature_flag) }
+ .to raise_error(QA::Runtime::Feature::UnknownFeatureFlagError)
+ end
+ end
+
+ context 'with definition files' do
+ context 'when no features are found via the API' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[]'))
+ allow(Dir).to receive(:glob).and_return(['file_path'])
+ allow(File).to receive(:read).and_return(definition)
+ end
+
+ context 'with a default enabled defintion' do
+ let(:definition) { 'default_enabled: true' }
+
+ it 'returns a default enabled flag' do
+ expect(described_class.enabled?(feature_flag)).to be true
+ end
+ end
+
+ context 'with a default disabled defintion' do
+ let(:definition) { 'default_enabled: false' }
+
+ it 'returns a default disabled flag' do
+ expect(described_class.enabled?(feature_flag)).to be false
+ end
+ end
+ end
+
+ context 'when the feature is found via the API' do
+ before do
+ allow(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features")
+ .and_return(request)
+ allow(described_class)
+ .to receive(:get)
+ .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a_flag", "state": "on" }]'))
+ end
+
+ it 'returns the value from the API not the definition file' do
+ expect(Dir).not_to receive(:glob)
+ expect(File).not_to receive(:read)
+
+ expect(described_class.enabled?(feature_flag)).to be true
+ end
+ end
+ end
end
end
diff --git a/qa/spec/runtime/logger_spec.rb b/qa/spec/runtime/logger_spec.rb
index f0fcfa0564e..652037a7041 100644
--- a/qa/spec/runtime/logger_spec.rb
+++ b/qa/spec/runtime/logger_spec.rb
@@ -2,6 +2,6 @@
RSpec.describe QA::Runtime::Logger do
it 'returns logger instance' do
- expect(described_class.logger).to be_an_instance_of(::Logger)
+ expect(described_class.logger).to be_an_instance_of(ActiveSupport::Logger)
end
end
diff --git a/qa/spec/service/shellout_spec.rb b/qa/spec/service/shellout_spec.rb
new file mode 100644
index 00000000000..9d7adeb0e94
--- /dev/null
+++ b/qa/spec/service/shellout_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe Service::Shellout do
+ let(:wait_thread) { instance_double('Thread') }
+ let(:errored_wait) { instance_double('Process::Status', exited?: true, exitstatus: 1) }
+ let(:non_errored_wait) { instance_double('Process::Status', exited?: true, exitstatus: 0) }
+ let(:stdin) { StringIO.new }
+ let(:stdout) { [+'logged in as user with password secret'] }
+
+ context 'when masking secrets' do
+ before do
+ allow(Open3).to receive(:popen2e).and_yield(stdin, stdout, wait_thread)
+ end
+
+ it 'masks command secrets on CommandError' do
+ expect(wait_thread).to receive(:value).twice.and_return(errored_wait)
+
+ expect { subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) }
+ .to raise_error(Service::Shellout::CommandError) do |error|
+ expect(error.to_s).to include('Command: `docker login -u **** -p ****` failed')
+ end
+ end
+
+ it 'masking secrets is optional' do
+ expect(wait_thread).to receive(:value).twice.and_return(errored_wait)
+
+ expect { subject.shell('docker pull ruby:3') }.to raise_error(Service::Shellout::CommandError) do |error|
+ expect(error.to_s).to include('Command: `docker pull ruby:3` failed')
+ end
+ end
+
+ it 'masks secrets when yielding output' do
+ expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)
+
+ subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) do |output|
+ expect(output).not_to be(nil)
+ expect(output).to eql('logged in as **** with password ****')
+ end
+ end
+
+ it 'masks secrets in debug logs' do
+ expect(Runtime::Logger).to receive(:debug).with(/logged in as \*\*\*\* with password \*\*\*\*/)
+ expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait)
+
+ subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user])
+ end
+
+ it 'masks secrets in error logs' do
+ expect(Runtime::Logger).to receive(:error).with(/logged in as \*\*\*\* with password \*\*\*\*/)
+ expect(wait_thread).to receive(:value).twice.and_return(errored_wait)
+
+ expect { subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) }
+ .to raise_error(Service::Shellout::CommandError)
+ end
+ end
+ end
+end
diff --git a/qa/spec/specs/helpers/feature_flag_spec.rb b/qa/spec/specs/helpers/feature_flag_spec.rb
index a1300ecf073..491fc22f026 100644
--- a/qa/spec/specs/helpers/feature_flag_spec.rb
+++ b/qa/spec/specs/helpers/feature_flag_spec.rb
@@ -147,6 +147,28 @@ RSpec.describe QA::Specs::Helpers::FeatureFlag do
it_behaves_like 'skips with given feature flag metadata', { name: 'global_ff', scope: :global }
end
+ context 'when run on pre' do
+ before(:context) do
+ QA::Runtime::Scenario.define(:gitlab_address, 'https://pre.gitlab.com')
+ end
+
+ context 'for only one test in the example group' do
+ it 'only skips specified test and runs all others' do
+ group = describe_successfully 'Feature flag set for one test' do
+ it('is skipped', feature_flag: { name: 'single_test_ff', scope: :group }) {}
+ it('passes') {}
+ end
+
+ expect(group.examples[0].execution_result.status).to eq(:pending)
+ expect(group.examples[1].execution_result.status).to eq(:passed)
+ end
+ end
+
+ it_behaves_like 'skips with given feature flag metadata', { name: 'actor_ff', scope: :project }
+
+ it_behaves_like 'skips with given feature flag metadata', { name: 'global_ff', scope: :global }
+ end
+
# The nightly package job, for example, does not run against a live environment with
# a defined gitlab_address. In this case, feature_flag tag logic can be safely ignored
context 'when run without a gitlab address specified' do
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index d5e442acfe7..dd013497367 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -1,14 +1,18 @@
# frozen_string_literal: true
RSpec.describe QA::Specs::Runner do
- shared_examples 'excludes orchestrated, transient, and geo' do
- it 'excludes the orchestrated, transient, and geo tags, and includes default args' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ shared_examples 'excludes default skipped, and geo' do
+ it 'excludes the default skipped and geo tags, and includes default args' do
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
+ before do
+ stub_const('DEFAULT_SKIPPED_TAGS', %w[--tag ~orchestrated --tag ~transient --tag ~sanity_feature_flags].freeze)
+ end
+
describe '#perform' do
before do
allow(QA::Runtime::Browser).to receive(:configure!)
@@ -17,13 +21,15 @@ RSpec.describe QA::Specs::Runner do
QA::Runtime::Scenario.define(:klass, "QA::Scenario::Test::Instance::All")
end
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
context 'when tty is set' do
subject { described_class.new.tap { |runner| runner.tty = true } }
it 'sets the `--tty` flag' do
- expect_rspec_runner_arguments(['--tty', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ ['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -39,7 +45,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'sets the `--dry-run` flag' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
subject.perform
end
@@ -73,7 +82,10 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } }
it 'includes the option value in the file name' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' }
@@ -98,7 +110,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'sets the `--dry-run` flag' do
- expect_rspec_runner_arguments(['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], [$stderr, anything])
+ expect_rspec_runner_arguments(
+ ['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS],
+ [$stderr, anything]
+ )
subject.perform
end
@@ -125,7 +140,9 @@ RSpec.describe QA::Specs::Runner do
subject { described_class.new.tap { |runner| runner.tags = %i[orchestrated github] } }
it 'focuses on the given tags' do
- expect_rspec_runner_arguments(['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ ['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -144,8 +161,8 @@ RSpec.describe QA::Specs::Runner do
context 'when "qa/specs/features/foo" is set as options' do
subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } }
- it 'passes the given tests path and excludes the orchestrated, transient, and geo tags' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', 'qa/specs/features/foo'])
+ it 'passes the given tests path and excludes the default skipped, and geo tags' do
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', 'qa/specs/features/foo'])
subject.perform
end
@@ -167,7 +184,7 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_signup_disabled tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
@@ -179,7 +196,7 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes the skip_live_env tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS])
subject.perform
end
end
@@ -212,7 +229,10 @@ RSpec.describe QA::Specs::Runner do
end
it 'includes default args and excludes all unsupported tags' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *excluded_feature_tags_except(feature), *described_class::DEFAULT_TEST_PATH_ARGS])
+ expect_rspec_runner_arguments(
+ DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *excluded_feature_tags_except(feature),
+ *described_class::DEFAULT_TEST_PATH_ARGS]
+ )
subject.perform
end
@@ -237,11 +257,11 @@ RSpec.describe QA::Specs::Runner do
end
end
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
end
context 'when features are not specified' do
- it_behaves_like 'excludes orchestrated, transient, and geo'
+ it_behaves_like 'excludes default skipped, and geo'
end
end
diff --git a/qa/spec/support/page_error_checker_spec.rb b/qa/spec/support/page_error_checker_spec.rb
index 7c8aaeb182a..ab7014f4677 100644
--- a/qa/spec/support/page_error_checker_spec.rb
+++ b/qa/spec/support/page_error_checker_spec.rb
@@ -8,23 +8,37 @@ RSpec.describe QA::Support::PageErrorChecker do
describe '.report!' do
context 'reports errors' do
let(:expected_chrome_error) do
- "Error Code 500\n\n"\
- "chrome errors\n\n"\
- "Path: #{test_path}\n\n"\
- "Logging: foo123"
+ <<~MSG
+ Error Code: 500
+
+ chrome errors
+
+ Path: #{test_path}
+
+ Logging: foo123
+ MSG
end
let(:expected_basic_error) do
- "Error Code 500\n\n"\
- "foo status\n\n"\
- "Path: #{test_path}\n\n"\
- "Logging: foo123"
+ <<~MSG
+ Error Code: 500
+
+ foo status
+
+ Path: #{test_path}
+
+ Logging: foo123
+ MSG
end
let(:expected_basic_404) do
- "Error Code 404\n\n"\
- "foo status\n\n"\
- "Path: #{test_path}"
+ <<~MSG
+ Error Code: 404
+
+ foo status
+
+ Path: #{test_path}
+ MSG
end
it 'reports error message on chrome browser' do
@@ -34,7 +48,10 @@ RSpec.describe QA::Support::PageErrorChecker do
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:chrome)
- expect { QA::Support::PageErrorChecker.report!(page, 500) }.to raise_error(RuntimeError, expected_chrome_error)
+ expect { QA::Support::PageErrorChecker.report!(page, 500) }.to raise_error(
+ QA::Support::PageErrorChecker::PageError,
+ expected_chrome_error
+ )
end
it 'reports basic message on non-chrome browser' do
@@ -44,7 +61,10 @@ RSpec.describe QA::Support::PageErrorChecker do
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:firefox)
- expect { QA::Support::PageErrorChecker.report!(page, 500) }.to raise_error(RuntimeError, expected_basic_error)
+ expect { QA::Support::PageErrorChecker.report!(page, 500) }.to raise_error(
+ QA::Support::PageErrorChecker::PageError,
+ expected_basic_error
+ )
end
it 'does not report failure metadata on non 500 error' do
@@ -56,7 +76,10 @@ RSpec.describe QA::Support::PageErrorChecker do
allow(page).to receive(:current_path).and_return(test_path)
allow(QA::Runtime::Env).to receive(:browser).and_return(:firefox)
- expect { QA::Support::PageErrorChecker.report!(page, 404) }.to raise_error(RuntimeError, expected_basic_404)
+ expect { QA::Support::PageErrorChecker.report!(page, 404) }.to raise_error(
+ QA::Support::PageErrorChecker::PageError,
+ expected_basic_404
+ )
end
end
end
@@ -182,9 +205,10 @@ RSpec.describe QA::Support::PageErrorChecker do
"</div>"
end
- let(:error_500_str) { "<h1> 500 </h1>"}
- let(:backtrace_str) {"<body><section class=\"backtrace\">foo</section></body>"}
- let(:no_error_str) {"<body>no 404 or 500 or backtrace</body>"}
+ let(:error_500_str) { "<head><title>Something went wrong (500)</title></head><body><h1> 500 </h1></body>" }
+ let(:project_name_500_str) { "<head><title>Project</title></head><h1 class=\"home-panel-title gl-mt-3 gl-mb-2\" itemprop=\"name\">qa-test-2022-05-25-12-12-16-d4500c2e79c37289</h1>" }
+ let(:backtrace_str) { "<head><title>Error::Backtrace</title></head><body><section class=\"backtrace\">foo</section></body>" }
+ let(:no_error_str) { "<head><title>Nothing wrong here</title></head><body>no 404 or 500 or backtrace</body>" }
it 'calls report with 404 if 404 found' do
allow(page).to receive(:html).and_return(error_404_str)
@@ -207,6 +231,13 @@ RSpec.describe QA::Support::PageErrorChecker do
expect(QA::Support::PageErrorChecker).to receive(:report!).with(page, 500)
QA::Support::PageErrorChecker.check_page_for_error_code(page)
end
+ it 'does not call report if 500 found in project name' do
+ allow(page).to receive(:html).and_return(project_name_500_str)
+ allow(Nokogiri::HTML).to receive(:parse).with(project_name_500_str).and_return(NokogiriParse.parse(project_name_500_str))
+
+ expect(QA::Support::PageErrorChecker).not_to receive(:report!)
+ QA::Support::PageErrorChecker.check_page_for_error_code(page)
+ end
it 'does not call report if no 404, 500 or backtrace found' do
allow(page).to receive(:html).and_return(no_error_str)
allow(Nokogiri::HTML).to receive(:parse).with(no_error_str).and_return(NokogiriParse.parse(no_error_str))
@@ -234,7 +265,7 @@ RSpec.describe QA::Support::PageErrorChecker do
it 'returns error report array of log messages' do
expect(QA::Support::PageErrorChecker.error_report_for([LogOne, LogTwo]))
- .to eq(%W(foo\n bar))
+ .to eq(%W[foo\n bar])
end
end
@@ -246,6 +277,7 @@ RSpec.describe QA::Support::PageErrorChecker do
before do
allow(Capybara).to receive(:current_session).and_return(session)
+ allow(QA::Runtime::Env).to receive(:can_intercept?).and_return(true)
end
it 'logs from the error cache' do
diff --git a/qa/spec/support/wait_for_requests_spec.rb b/qa/spec/support/wait_for_requests_spec.rb
index 221d61ea2b4..3204333fc0e 100644
--- a/qa/spec/support/wait_for_requests_spec.rb
+++ b/qa/spec/support/wait_for_requests_spec.rb
@@ -16,22 +16,6 @@ RSpec.describe QA::Support::WaitForRequests do
end
end
- context 'when skip_finished_loading_check is true' do
- it 'does not call finished_loading?' do
- subject.wait_for_requests(skip_finished_loading_check: true)
-
- expect(subject).not_to have_received(:finished_loading?)
- end
- end
-
- context 'when skip_resp_code_check is defaulted to false' do
- it 'call report' do
- subject.wait_for_requests
-
- expect(QA::Support::PageErrorChecker).to have_received(:check_page_for_error_code).with(Capybara.page)
- end
- end
-
context 'when skip_resp_code_check is true' do
it 'does not parse for an error code' do
subject.wait_for_requests(skip_resp_code_check: true)
diff --git a/qa/tasks/contracts.rake b/qa/tasks/contracts.rake
deleted file mode 100644
index 682ec0e2e21..00000000000
--- a/qa/tasks/contracts.rake
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-require 'pact/tasks/verification_task'
-
-contracts = File.expand_path('../contracts', __dir__)
-provider = File.expand_path('provider', contracts)
-
-# rubocop:disable Rails/RakeEnvironment
-namespace :contracts do
- namespace :mr do
- Pact::VerificationTask.new(:metadata) do |pact|
- pact.uri(
- "#{contracts}/contracts/merge_request_page-merge_request_metadata_endpoint.json",
- pact_helper: "#{provider}/spec/metadata_helper.rb"
- )
- end
-
- Pact::VerificationTask.new(:discussions) do |pact|
- pact.uri(
- "#{contracts}/contracts/merge_request_page-merge_request_discussions_endpoint.json",
- pact_helper: "#{provider}/spec/discussions_helper.rb"
- )
- end
-
- Pact::VerificationTask.new(:diffs) do |pact|
- pact.uri(
- "#{contracts}/contracts/merge_request_page-merge_request_diffs_endpoint.json",
- pact_helper: "#{provider}/spec/diffs_helper.rb"
- )
- end
-
- desc 'Run all merge request contract tests'
- task 'test:merge_request', :contract_mr do |_t, arg|
- raise(ArgumentError, 'Merge request contract tests require contract_mr to be set') unless arg[:contract_mr]
-
- ENV['CONTRACT_MR'] = arg[:contract_mr]
- errors = %w[metadata discussions diffs].each_with_object([]) do |task, err|
- Rake::Task["contracts:mr:pact:verify:#{task}"].execute
- rescue StandardError, SystemExit
- err << "contracts:mr:pact:verify:#{task}"
- end
-
- raise StandardError, "Errors in tasks #{errors.join(', ')}" unless errors.empty?
- end
- end
-end
-# rubocop:enable Rails/RakeEnvironment
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index cfc11d0ba24..fe9a9c4586f 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -16,9 +16,15 @@ namespace :knapsack do
exit QA::Specs::KnapsackRunner.run(rspec_args)
end
- desc "Download latest knapsack report"
+ desc "Download latest knapsack report or multiple reports passed via QA_KNAPSACK_REPORTS env variable"
task :download do
- QA::Support::KnapsackReport.download_report
+ next QA::Support::KnapsackReport.download_report unless ENV["QA_KNAPSACK_REPORTS"]
+
+ ENV["QA_KNAPSACK_REPORTS"].split(",").each do |report_name|
+ QA::Support::KnapsackReport.new(report_name).download_report
+ rescue StandardError => e
+ QA::Runtime::Logger.error(e)
+ end
end
desc "Merge and upload knapsack report"
@@ -28,7 +34,7 @@ namespace :knapsack do
desc "Report long running spec files"
task :notify_long_running_specs do
- QA::Support::LongRunningSpecReporter.execute
+ QA::Tools::LongRunningSpecReporter.execute
end
end
# rubocop:enable Rails/RakeEnvironment
diff --git a/qa/tmp/.gitignore b/qa/tmp/.gitignore
new file mode 100644
index 00000000000..cec9082b6d6
--- /dev/null
+++ b/qa/tmp/.gitignore
@@ -0,0 +1,3 @@
+*
+
+!.gitignore