diff options
Diffstat (limited to 'qa')
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 |