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